I had a class like:
public class EmailAddress {
public String value;
public String tld() {...}
public String host() {...}
public String mailbox() {...}
}
Now I use this class in an Object / Entity:
#Entity
public class Customer {
public String name;
public EmailAddress mail;
}
Now, when I do a rest service for Customer, I get this format:
{
"id": 1,
"name": "Test",
"email": {
"value": "test#test.de"
}
}
But I only want "email": "test#test.de"
{
"id": 1,
"name": "Test",
"email": "test#test.de"
}
What I must do? I use Spring Boot and Hibernate Entities.
Thank you for any support
You should use DTO class in request handling and make mappings from DTO to Entity and backwards, e.g.:
public class CustomerDTO {
private Integer id;
private String name;
private String email;
}
You should use DataTransferObjects for your (REST) APIs.
The DTOs only contain the fields the interface should provide (or receive).
When receiving objects from the client and before returning the object from your Controller you can convert the DTOs to your domain model (Which could be your JPA entites classes).
Example for a controller method. We assume you get an object from an user-editor which contains all data you want to update in your database-objects and return the updated company DTO:
#PutMapping
public CustomerDto updateCustomer(CustomerEditorDto updatedCustomerDto) {
Customer updatedCustomer = CustomerConverter.convert(updatedCustomerDto);
updatedCustomer = customerService.updateCustomer(updatedCustomer);
return CustomerConverter.convert(updatedCustomer);
}
and your Converter class:
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CustomerConverter {
public static CustomerDto convert(Customer customer) {
CustomerDto result = null;
if (customer != null) {
// TODO: set fields in result-dto
}
return result;
}
public static Customer convert(CustomerEditorDto customer) {
Customer result = null;
if (customer != null) {
// TODO set fields in result;
}
return result;
}
}
and here are the DTOs
#Getter
#Setter
public class CustomerDto {
private Integer id;
private String name;
private String email;
}
#Getter
#Setter
public class CustomerEditorDto {
private Integer id;
private String firstName;
private String lastName;
private String email;
private String otherPropertyOrStuff;
}
This way you can separate the API modell from your JPA entites. You can use the same models for input/output. And you can even use a different model to work with inside your services and the finally convert them into your JPA entites, before persisting the data (or after reading the data).
There are tools which can take care of the conversion, like mapstruct.
* The above annotations #Getter, #Setter, ... are from project lombok and very are handy to generate boiler-plate code automatically.
I found an other easier solution, use a JsonSerializer on the entity Property:
#JsonSerialize(using = EmailAddressSerializer.class)
private EmailAddress email;
The serializer class:
public class EmailAddressSerializer extends StdSerializer<EmailAddress> {
public EmailAddressSerializer() {
super(EmailAddress.class);
}
protected EmailAddressSerializer(Class<EmailAddress> t) {
super(t);
}
#Override
public void serialize(EmailAddress email,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(email.value);
}
}
Related
I'm a QA writing some tests using Rest Assured DSL.
This is my first attempt at using Lombok to deserialize a POJO for use in the JSON Payload.
This way of building my data object, Customer, seems very cumbersome. As the test is failing with a 400, I assume I am not serializing it correctly and I'm unclear how to view the payload as JSON.
I'm not using an explicit mapping, so assume Rest Assured is using GSON by default.
Given my POJO:
import lombok.Data;
#Data
public class Customer {
private String employeeCode;
private String customer;
private String firstName;
private String lastName;
private String title;
private String dob;
private String employeeId;
}
...And example payload I need to send:
{
"employeeCode": "18ae56",
"customer": {
"firstName": "John",
"lastName": "Smith",
"title": "Mr",
"dob": "1982-01-08",
"employeeId": "2898373"
}
}
My example test is:
#BeforeClass
public static void createRequestSpecification(){
requestSpec = new RequestSpecBuilder()
.setBaseUri("https://employee-applications.company.com")
.setContentType(ContentType.JSON)
.build();
}
#Test
public void createApplicationForNewCustomer(){
Customer customer = Customer.builder().build();
customer.setEmployeeCode("18ae56");
customer.setFirstName("John");
customer.setLastName("Smith");
customer.setTitle("Mr");
customer.setDob("1982-01-08");
customer.setEmployeeId("2898373");
given().
spec(requestSpec).
and().
body(customer).
when().
post("/api/v6/applications").
then().
assertThat().statusCode(201);
}
Your POJO is incorrect and obviously the serialized JSON is not of the expected format
You should have two classes
Below is how your POJO should look like to generate the given JSON structure
#Data
public static class Customer {
#JsonProperty("firstName")
private String firstName;
#JsonProperty("lastName")
private String lastName;
#JsonProperty("title")
private String title;
#JsonProperty("dob")
private String dob;
#JsonProperty("employeeId")
private String employeeId;
}
#Data
public static class Example {
#JsonProperty("employeeCode")
public String employeeCode;
#JsonProperty("customer")
public Customer customer;
}
and your test method
Example e = new Example();
e.setEmployeeCode("18ae56");
Customer c = new Customer();
c.setFirstName("John");
c.setLastName("Smith");
c.setTitle("Mr");
c.setDob("1982-01-08");
c.setEmployeeId("2898373");
e.setCustomer(c);
given().spec(requestSpec).and().body(e).when().post("/api/v6/applications").then().assertThat()
Easiest ways to test :
String abc = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(e);
System.out.println(abc);
or
System.out.println(new Gson().toJson(e));
I have a requirement where I need a subclass as object while creating a json payload.
EventBase
public class EventBase {
#JsonProperty("event_id")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
PaymentCapture (the sub class)
#JsonTypeName("resource")
public class PaymentCapture extends EventBase {
#JsonProperty("parent_payment")
private String parentPayment;
public String getParentPayment() {
return parentPayment;
}
public void setParentPayment(String parentPayment) {
this.parentPayment = parentPayment;
}
}
And I need a json payload in below form:
{
"id": "someId",
"resource": {
"parent_payment": "23434"
}
}
I can understand this violates inheritance relationship, but just want to know if there is any solution available or not.
The closest I could get when having similar problem was creating an adapter class. This solution prints one extra property which might be possible to be ignored if for example some inheritance was allowed but I assume that not and use just the declared classes in addition to the adapter, which is like:
#RequiredArgsConstructor
public class PaymentCaptureAdapterClass {
#NonNull
#JsonProperty
private PaymentCapture resource;
#JsonProperty
private String getId() {
return resource.getId();
}
}
using this with code:
ObjectMapper om = new ObjectMapper();
om.enable(SerializationFeature.INDENT_OUTPUT);
PaymentCapture pc = new PaymentCapture();
pc.setId("someId");
pc.setParentPayment("23434");
log.info("\n{}", om.writeValueAsString(new AdapterClass(pc)));
prints something like:
{
"resource" : {
"event_id" : "someId", // might be able to be ignored
"parent_payment" : "23434"
},
"id" : "someId"
}
I have in my controller:
#RestController
public class OneTwoController {
private OnTwoService _service;
//... more code
#PostMapping("/api/one-two")
#CrossOrigin
public ResponseEntity<ServiceResponse> save(#RequestBody OneTwo model) {
return ResponseEntity.ok().body( _service.Save(model));
}
In my entity:
#Entity(name = "OneTwo")
#Where (clause = "deleted='false'")
public class OneTwo{
#EmbeddedId
private OneTwoKey_id;
public OneTwo(OneTwoKey id) {
this._id = id;
}
#JsonProperty("oneTwo")
public void setId(OneTwoKey value) {
this._id = value;
}
The OneTwoKey class:
public class OneTwoKey implements Serializable {
#Column(name = "OneID")
private int _oneID;
#Column(name = "TwoID")
private int _twoID;
public OneTwoKey(int oneID, int twoID) {
this._oneID = oneID;
this._twoID = twoID;
}
}
The json that I send to the Rest API:
{
"oneTwo": {
"oneID": 83,
"twoID": 69
},
"deleted": true
}
The issue is that both ids arrive null, so the service can't do the insert on the DB.
How can I deal with those cases when the ids are more than one?
Try adding setters in the OneTwoKey class to make it easier for the JSON deserializer:
#JsonProperty("oneID")
public void setOneID(int oneID) {
this._oneID = oneID;
}
#JsonProperty("twoID")
public void setTwoID(int twoID) {
this._twoID = twoID;
}
Another solution is to create a DTO, use it to receive the data in the controller and then convert it to your entity:
public class OneTwoDTO {
private Map<String, Int> oneTwo;
private boolean deleted;
// setters & getters
}
Simply what you can do is instead of using
public ResponseEntity<ServiceResponse> save(#RequestBody OneTwo model) {
you can use
public ResponseEntity<ServiceResponse> save(#RequestBody String model) {
Now convert the String to json and get all the key value pairs, it would be easier if you have dynamic number of variables and you want to capture them all.
or you can use tools like jsonschema2pojo whick take a json schema and generate a pojo. In the json schema if you set
"additionalProperties": true
you can capture all the values.
Could you make sure the problem is not because of case sensitivity?
Lower case the column names. Also could you use public access on those variables as well? These are my initial guesses as to why the payload is not being binded correctly.
public class OneTwoKey implements Serializable {
#Column(name = "oneID")
public int _oneID;
#Column(name = "twoID")
public int _twoID;
I am using spring boot 1.5.1.RELEASE, with jackson as MessageConverter.
One of my POJO is looking something like following:
#Getter
#AllArgsConstructor
public class Pojo1 {
#Id
private final String id;
private final NestedValue payload;
}
public class NestedValue {
private final String value;
public NestedValue(String value) {
this.value = value;
}
#JsonCreator
public static NestedValue(String value) {
return new NestedValue(value);
}
#JsonValue
public String getValue() {
return value;
}
}
#RepositoryRestResource(collectionResourceRel = 'pojos', path='pojos')
public interface Pojo1Repository extends MongoRepository<Pojo, String> {
}
I am expecting above to be serialized as this:
{
"payload": "value projected by getValue()"
}
Instead, I am getting as below:
{
"payload": {
"content": "value projected by getValue()"
}
}
If I test it with default ObjectMapper, POJO is serialized as I expected.
Is there a hidden jackson feature that is configured by Spring???
I am trying to create a springboot application using MongoDB and a Rest controller and connect objects together using DBRef instead of classic Jpa annotations like OneToMany etc. The purpose is to print all the bookmarks for a specific account. The list of bookmarks is found by the username but it seems that it doesn't work.
These are my classes:
#Document
public class Account {
#DBRef
private Set<Bookmark> bookmarkSet = new HashSet<>();
#Id
private String id;
#JsonIgnore
private String username;
private String password;
public Account(String username, String password) {
this.username = username;
this.password = password;
}
public void setBookmarkSet(Set<Bookmark> bookmarkSet) {
this.bookmarkSet = bookmarkSet;
}
public String getId() {
return id;
}
}
#Document
public class Bookmark {
#DBRef
#JsonIgnore
private Account account;
#Id
private String id;
private String uri;
private String description;
public Bookmark(Account account, String uri, String description) {
this.account = account;
this.uri = uri;
this.description = description;
}
public Account getAccount() {
return account;
}
public String getId() {
return id;
}
public String getUri() {
return uri;
}
public String getDescription() {
return description;
}
}
repositories:
public interface AccountRepository extends MongoRepository<Account, Long> {
Optional<Account> findOneByUsername(String username);
}
public interface BookmarkRepository extends MongoRepository<Bookmark, Long> {
Collection<Bookmark> findByAccountUsername(String username);
}
And RestController:
#RestController
#RequestMapping("/{userId}/bookmarks")
public class BookmarkRestController {
private final AccountRepository accountRepository;
private final BookmarkRepository bookmarkRepository;
#Autowired
public BookmarkRestController(AccountRepository accountRepository, BookmarkRepository bookmarkRepository) {
this.accountRepository = accountRepository;
this.bookmarkRepository = bookmarkRepository;
}
#RequestMapping(value = "/{bookmarkId}", method = RequestMethod.GET)
Bookmark readBookmark(#PathVariable String userId, #PathVariable Long bookmarkId) {
this.validateUser(userId);
return bookmarkRepository.findOne(bookmarkId);
}
#RequestMapping(method = RequestMethod.GET)
Collection<Bookmark> readBookmarks(#PathVariable String userId) {
this.validateUser(userId);
return this.bookmarkRepository.findByAccountUsername(userId);
}
private void validateUser(String userId) {
this.accountRepository.findOneByUsername(userId).orElseThrow(() -> new UserNotFoundException(userId));
}
}
After I run the application I get this error:
Invalid path reference account.username! Associations can only be pointed to directly or via their id property!
I'm not sure you have the right schema design. I assume you've modeled you objects based on a relational database type model, where the data is normalised and data is split across multiple tables, with relationships captured using Ids. With MongoDB you can structure and store your data with the heirarchy simply contained in within the one document.
So in your example the Bookmark would not be a Document itself, but would be a sub document of the Account. Remove the #Document annotation from the Bookmark object, and the #DBRef annotations, and simply store the Bookmarks within the Account document.
This would give you a schema more like this:
{
"_id": 1,
"bookmarkSet": [
{
"uri": "http://www.foo.com",
"description": "foo"
},
{
"uri": "http://www.bar.com",
"description": "bar"
}
],
"username": "John",
"password": "password"
}
*Note: if you make the bookmarks sub documents you can remove the _id member from the Bookmark object
The best design will depend on how many bookmarks you expect each account to have. If its only a few bookmarks then what I suggested would work well. If you have thousands then you might want to structure it differently. There are lots of articles about schema design in NoSQL database. This one covers the options for embedding subdocuments quite well:
http://blog.mongodb.org/post/87200945828/6-rules-of-thumb-for-mongodb-schema-design-part-1