I'm using the DTO pattern for managing HTTP bodies in a Spring Boot REST application. I have seperate DTOs for requests and responses as the response contains additional data. I'm using inheritance for the response DTO as the additional data included is the same for all response objects. The structure is as following (annotations and methods ommited for clearance):
public abstract class BaseResponseDTO {
protected UUID id;
protected Integer version;
protected Date created;
protected Date modified;
}
public class RequestUserDTO {
private String firstName;
private String lastName;
}
public class ResponseUserDTO extends BaseResponseDTO {
private String firstName;
private String lastName;
}
There is obvious code duplication here. It would be ideal for the ResponseUserDTO to extend both BaseResponseDTO and RequestUserDTO which is not allowed.
Other option would be to use composition and have something as follows:
public abstract class BaseResponseDTO {
protected UUID id;
protected Integer version;
protected Date created;
protected Date modified;
}
public class UserDTO {
private String firstName;
private String lastName;
}
public class RequestUserDTO {
private UserDTO payload;
}
public class ResponseUserDTO extends BaseResponseDTO {
private UserDTO payload;
}
The problems I have with this approach are:
It still does not prevent code duplication
It forces client to wrap the body in payload
What can be done about that?
I think you could replace both your ResponseUserDTO and RequestUserDTO with UserDTO and extend BaseResponseDTO on it, when you send a request body using UserDTO the code won't care if you send it along with the BaseResponseDTO or not (since I didn't see any validation here), so if you not gonna use those in your request function it will be just fine
public abstract class BaseResponseDTO {
protected UUID id;
protected Integer version;
protected Date created;
protected Date modified;
}
public class UserDTO extends BaseResponseDTO {
private String firstName;
private String lastName;
}
Related
In my spring boot project, I noticed a strange Jackson behavior. I searched over internet, found out what to do, but haven't found out why.
UserDto:
#Setter
#Getter
#AllArgsConstructor
public class UserDto {
private String username;
private String email;
private String password;
private String name;
private String surname;
private UserStatus status;
private byte[] avatar;
private ZonedDateTime created_at;
}
Adding a new user works just fine.
TagDto:
#Setter
#Getter
#AllArgsConstructor
public class TagDto {
private String tag;
}
Trying to add a new tag ends with an error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of TagDto (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
The solution to the problem was to add zero-arg constructor to the TagDto class.
Why does Jackson require no-arg constructor for deserialization in TagDto, while working just fine with UserDto?
Used same method for adding both.
My Tag and User entities are both annotated with
#Entity
#Setter
#Getter
#NoArgsConstructor
and have all args constructors:
#Entity
#Setter
#Getter
#NoArgsConstructor
public class User extends AbstractModel {
private String username;
private String password;
private String email;
private String name;
private String surname;
private UserStatus status;
#Lob
private byte[] avatar;
#Setter(AccessLevel.NONE)
private ZonedDateTime created_at;
public User(final String username, final String password, final String email, final String name, final String surname) {
this.username = username;
this.password = password;
this.email = email;
this.name = name;
this.surname = surname;
this.created_at = ZonedDateTime.now();
}
}
#Entity
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class Tag extends AbstractModel {
private String tag;
}
#MappedSuperclass
#Getter
public abstract class AbstractModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}
Entity generation:
#PostMapping(path = "/add")
public ResponseEntity<String> add(#Valid #RequestBody final D dto) {
this.abstractModelService.add(dto);
return new ResponseEntity<>("Success", HttpStatus.CREATED);
}
public void add(final D dto) {
//CRUD repository save method
this.modelRepositoryInterface.save(this.getModelFromDto(dto));
}
#Override
protected Tag getModelFromDto(final TagDto tagDto) {
return new Tag(tagDto.getTag());
}
#Override
protected User getModelFromDto(final UserDto userDto) {
return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
}
Error occurs when parsing JSON
{"tag":"example"}
sent via postman localhost:8081/tag/add, returns
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
I am using Lombok v1.18.12 and Spring boot 2.3.3.RELEASE with Jackson v2.11.2.
TL;DR: Solution is at the end.
Jackson supports multiple ways of creating POJOs. The following lists the most common ways, but it likely not a complete list:
Create instance using no-arg constructor, then call setter methods to assign property values.
public class Foo {
private int id;
public int getId() { return this.id; }
#JsonProperty
public void setId(int id) { this.id = id; }
}
Specifying #JsonProperty is optional, but can be used to fine-tune the mappings, together with annotations like #JsonIgnore, #JsonAnyGetter, ...
Create instance using constructor with arguments.
public class Foo {
private int id;
#JsonCreator
public Foo(#JsonProperty("id") int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Specifying #JsonCreator for the constructor is optional, but I believe it is required if there is more than one constructor. Specifying #JsonProperty for the parameters is optional, but is required for naming the properties if the parameter names are not included in the class file (-parameters compiler option).
The parameters imply that the properties are required. Optional properties can be set using setter methods.
Create instance using factory method.
public class Foo {
private int id;
#JsonCreator
public static Foo create(#JsonProperty("id") int id) {
return new Foo(id);
}
private Foo(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Create instance from text value using String constructor.
public class Foo {
private int id;
#JsonCreator
public Foo(String str) {
this.id = Integer.parseInt(id);
}
public int getId() {
return this.id;
}
#JsonValue
public String asJsonValue() {
return Integer.toString(this.id);
}
}
This is useful when a the POJO has a simply text representation, e.g. a LocalDate is a POJO with 3 properties (year, month, dayOfMonth), but is generally best serialized as a single string (yyyy-MM-dd format). #JsonValue identifies the method to be used during serialization, and #JsonCreator identifies the constructor/factory-method to be used during deserialization.
Note: This can also be used for single-value construction using JSON values other than String, but that is very rare.
Ok, that was the background information. What is happening for the examples in the question, it that UserDto works because there is only one constructor (so #JsonCreator is not needed), and many arguments (so #JsonProperty is not needed).
However, for TagDto there is only a single-argument constructor without any annotations, so Jackson classifies that constructor as a type #4 (from my list above), not a type #2.
Which means that it is expecting the POJO to be a value-class, where the JSON for the enclosing object would be { ..., "tag": "value", ... }, not { ..., "tag": {"tag": "example"}, ... }.
To resolve the issue, you need to tell Jackson that the constructor is a property initializing constructor (#2), not a value-type constructor (#4), by specifying #JsonProperty on the constructor argument.
This means that you cannot have Lombok create the constructor for you:
#Setter
#Getter
public class TagDto {
private String tag;
public TagDto(#JsonProperty("tag") String tag) {
this.tag = tag;
}
}
I am connecting with many social networks for login in my application.
I have one DTO for each social network response.
public class GoogleUserInfo {
private String id;
private String firstName;
private String lastName;
private String email;
private AgeRange ageRange;
// more specific fields
}
public class FacebookUserInfo {
private String id;
private String firstName;
private String lastName;
private String email;
private String picture;
// more specific fields
}
public class AppleUserInfo {
private String id;
private String firstName;
private String lastName;
private String email;
private Boolean emailVerified;
// more specific fields
}
In each social network connector, I make similar steps to fetch the information, so I thought I could go with some DTO as follows:
public class SocialNetworkInfo {
protected String id;
protected String firstName;
protected String lastName;
protected String email;
}
Social networks DTOs could extend this to obtain the common fields. Then I could use this generic DTO to implement an abstract connector that deals with all the duplicate logic between connectors (make request, parse response, etc...):
abstract class AbstractConnector {
abstract SocialNetworkInfo fetchUserInfo(String networkId);
...
}
But I realized that above, in my service layer, I would need those specific fields to make some changes and operations.
SocialNetworkInfo networkUserInfo = facebookConnector.fetchUserInfo(facebookId);
facebookService.updatePicture(networkUserInfo.getPicture()); // can't access this specific field
What do you think that's the best way to go through this situation without casting and avoiding logic or DTO duplication?
Would love to hear your thoughts.
Thanks!
According to your situation, all social network models have the same nature, so it's ok if you move common attributes to shared class like CommonSocialInfo. Then I would recommend to provide interface for the connectors like:
interface SocialNetworkConnector<T extends SocialNetworkInfo> {
T fetchUserInfo(String userId);
}
Of course for common functionality(for connectors) is great idea to define common abstract class that implements interface above (implement Template pattern). I see that you are using FacebookService and related connector separately. I think that good idea to use composition in this case and make SocialNetworkService dependent on it connector. In short, FacebookService depends on FacebookConnecter and so on. Just a quick example:
public class FacebookService implements SocialNetworkService {
private final SocialNetworkConnector<FacebookSocialInfo> connector;
...
}
And if you need to implement multiple social service, you can use Factory pattern to produce required service, quick example:
interface SocialNetworkServiceFactory {
SocialNetworkService getFacebookService();
...
}
If you need more detailed help or you have troubles with understanding of the idea - feel free to ask!
If you don't want to use inheritance, I'd suggest to consider composition. The code can look as follows:
public class SocialNetworkInfo {
private String id;
private String firstName;
private String lastName;
private String email;
}
public class GoogleUserInfo {
private SocialNetworkInfo socialNetworkInfo;
private AgeRange ageRange;
// more specific fields
}
public class FacebookUserInfo {
private SocialNetworkInfo socialNetworkInfo;
private String picture;
// more specific fields
}
public class AppleUserInfo {
private SocialNetworkInfo socialNetworkInfo;
private Boolean emailVerified;
// more specific fields
}
I have a model like this:
public class Employee {
#JsonProperty("emplyee_id")
private Integer id;
#JsonProperty("emplyee_first_name")
private String firstName;
#JsonProperty("emplyee_last_name")
private String lastName;
#JsonProperty("emplyee_address")
private String address;
#JsonProperty("emplyee_age")
private Byte age;
#JsonProperty("emplyee_level")
private Byte level;
//getters and setters
}
now I need to create two JSONs using this (only) model.
the first one must like this for example:
{
"employee_id":101,
"employee_first_name":"Alex",
"employee_last_name":"Light",
"employee_age":null,
"employee_address":null
}
and the second one must like this for example:
{
"employee_id":101,
"employee_level":5
}
by the way, I already tested #JsonIgnore and #JsonInclude(JsonInclude.Include.NON_NULL).
the problem of the first one (as much as I know) is, those fields can't be included in other JSONs (for example if level get this annotation, it won't be included in the second JSON)
and the problem of the second one is, null values can't be included in JSON.
so can I keep null values and prevent some other property to be included in JSON without creating extra models? if the answer is yes, so how can I do it? if it's not I really appreciate if anyone gives me the best solution for this state.
thanks very much.
it could be useful for you using #JsonView annotation
public class Views {
public static class Public {
}
public static class Base {
}
}
public class Employee {
#JsonProperty("emplyee_id")
#JsonView({View.Public.class,View.Base.class})
private Integer id;
#JsonProperty("emplyee_first_name")
#JsonView(View.Public.class)
private String firstName;
#JsonProperty("emplyee_last_name")
#JsonView(View.Public.class)
private String lastName;
#JsonProperty("emplyee_address")
private String address;
#JsonProperty("emplyee_age")
private Byte age;
#JsonProperty("emplyee_level")
#JsonView(View.Base.class)
private Byte level;
//getters and setters
}
in your json response add #JsonView(Public/Base.class) it will return based on jsonview annotations
//requestmapping
#JsonView(View.Public.class)
public ResponseEntity<Employee> getEmployeeWithPublicView(){
//do something
}
response:
{
"employee_id":101,
"employee_first_name":"Alex",
"employee_last_name":"Light",
"employee_age":null,
"employee_address":null
}
for the second one
//requestmapping
#JsonView(View.Base.class)
public ResponseEntity<Employee> getEmployeeWithBaseView(){
//do something
}
response
{
"employee_id":101,
"employee_level":5
}
I have two classes Employee and Address.
public class Employee implements java.io.Serializable {
private long id;
private String name;
private Address address;
private UserType userType;
private Date bdate;
public Employee() {
}
// getters and setters
}
public class Address implements java.io.Serializable {
private long id;
private Set<String> lines;
public Address() {
}
// getters and setters
}
I am using moxy to convert hibernate entities to json. I have enabled it in pom.xml. Also added #XmlRootElement on the top of entity. Below is my service code.
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("{/Id}")
public Employee test(#PathParam("Id") long Id)
{
return EmployeeDao.getEmployee(Id);
}
Right now I am getting 500 for this service. Any help will be appreciated.
I have a rather JSON response coming back from a solr instance....
{"responseHeader":
{"status":0,"QTime":1,"params":{"sort":"score asc","fl":"*,score",
"q":"{! score=distance}","wt":"json","fq":"description:motor","rows":"1"}},
"response":{"numFound":9,"start":0,"maxScore":6.8823843,"docs":
[{"workspaceId":2823,"state":"MN","address1":"1313 mockingbird Lane",
"address2":"","url":"http://mydomain.com/","city":"Minneapolis",
"country":"US","id":"399068","guid":"","geo":["45.540239, -98.580473"],
"last_modified":"2012-12-12T20:40:29Z","description":"ELEC MOTOR",
"postal_code":"55555","longitude":"-98.580473","latitude":"45.540239",
"identifier":"1021","_version_":1421216710751420417,"score":0.9288697}]}}
And I'm trying to map that to a java object:
public class Item extends BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
protected Integer workspaceId;
protected String name;
protected String description;
protected String identifier;
protected String identifierSort;
protected Address address;
protected String url;
/** getters and setters eliminated for brevity **/
}
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
protected String address1;
protected String address2;
protected String city;
protected String state;
protected String postalCode;
protected String country;
/** getters and setters eliminated for brevity **/
}
How do I map the address1, address2, city, state, etc... into the Address object in the Item object? I've been reading about Jackson annotations but nothing really jumps out at me as to where to begin.
If using Jackson 1.9 or higher you can use the #JsonUnwrapped annotation to handle this.
Here is an example of using it (largely lifted from Jackson's documentation):
public class Name {
private String first, last;
// Constructor, setters, getters
}
public class Parent {
private int age;
#JsonUnwrapped
private Name name;
// Constructor, setters, getters
}
public static void main(String[] args) {
try {
final ObjectMapper mapper = new ObjectMapper();
final Parent parent = mapper.readValue(new File(
"/path/to/json.txt"), Parent.class);
System.out.println(parent);
} catch (final Exception e) {
e.printStackTrace();
}
}
We ended up using Solrj - sort of.
We wrote our own SolrResult object that we fed to SolrJ like so:
List<SolrResult> solrResults = rsp.getBeans(SolrResult.class);
And then in SolrResult.java where we had complex or nested objects we just first used SolrJ annotation to get the field and then just set the value as needed...
#Field("address1")
public void setAddress1(String address1) {
this.item.getAddress().setAddress1(address1);
}
It wasn't hard just feels a bit messy, but it does work.