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;
}
}
Given the Source class as defined below:
class Source{
private String name;
private int age;
private List<Phone> phones;
// getters and setters
}
and the Phone class as defined below:
class Phone{
private Long id;
private String phoneNumber;
// getters and setters
}
and the Target class as defined below:
class Target{
private String name;
private int age;
private List<Long> idsPhones;
// getters and setters
}
I have an interface is:
#Mapper
interface MyMapper{
Target toTarget(Source source);
Source toSource(Target target);
}
How can I map the List of Phones from the Source class to a List of idsPhones in the Target Class and vice versa?
In order to achieve this you need to help MapStruct a bit by telling how to map from Phone into Long. And the reverse as well.
Your mapper needs to look something like:
#Mapper(uses = PhoneRepository.class)
interface MyMapper {
#Mapping(target = "idsPhones", source = "phones")
Target toTarget(Source source);
#InheritInverseMapping
Source toSource(Target target);
default Long fromPhone(Phone phone) {
return phone.getId();
}
}
If your PhoneRepository contains a method that accepts a Long and returns Phone then MapStruct will automatically know what to do and invoke that method.
I have a Base dto and a dto which extends it
public abstract class AbstractItem {
private String upc;
private String quantity;
//getter and setter for upc and quantity
}
public class OrderedItem extends AbstractItem {
private String orderId;
// getter and setter for orderId
}
Then I have a modal which I want to convert from OrderedItem. This modal extends a base modal
public abstract class AbstractModal {
private String qty;
public void setQty(String qty) {
this.qty = qty;
}
public String getQty() {
return qty;
}
}
public class ItemModal extends AbstractModal {
private String orderNbr;
private String upcNbr;
// getter and setter for orderNbr and upcNbr;
}
this is my mapper function using Mapstruct
#Mapper
public interface DtoMapper {
#Mappings({
#Mapping(source = "orderedItem.orderId" target = "orderNbr"),
#Mapping(source = "orderedItem.upc" target = "upcNbr"),
#Mapping(source = "orderedItem.quantity" target = "qty")
})
ItemModal convert(OrderedItem orderedItem);
}
During compilation I get an error Unknown property "qty" in result type test.modal.ItemModal.
Does Mapstruct support mapping fields of base class?
If yes, can you please tell me what is wrong with my code?
I am using java 8 and mapstruct 1.2.0
Edit: added getter and setter for qty in AbstractModal
I have a Mcq class associated to a MongoRepository, and I want to get an instance of my Mcq which apply several changes (Answers shuffle, Questions draw, etc). I declared my function "myMcq.getInstance()", but I can't do that because every time I want to send a Mcq in a ResponseEntity there is an error in the JSON output because Springboot thinks that there is a "instance" property in my class.
Here is my java class :
#Document(collection = "Mcqs")
public class Mcq {
#Id public String id;
#DBRef public User creator;
public String title;
public String categoryID;
public List<McqChapter> chapterList = new ArrayList<>();
public Difficulty difficulty;
public Mcq() {}
public Mcq(String title) {
this();
this.title = title;
}
public ArrayList<String> getQuestionsIDs() {
ArrayList<String> result = new ArrayList<>();
for (McqChapter chapter : chapterList) result.addAll(chapter.getQuestionIDs());
return result;
}
public McqInstance getInstance() {
return new McqInstance(this);
}
}
To prevent the error add #JsonIgnore to getInstance() method:
#JsonIgnore
public McqInstance getInstance() {
return new McqInstance(this);
}
Marker annotation that indicates that the annotated method or field is to be ignored by introspection-based serialization and deserialization functionality. That is, it should not be consider a "getter", "setter" or "creator".
I need to implement custom conversion for ID field in Company and Employee classes. I have already implemented custom converter extended from StrutsTypeConverter and it is successfully used to convert Company.ID field, but it does not work for Employee.ID.
Seems like the main problem is in conversion properties file. How should I specify converter class for employee ID field in conversion properties file?
MyAction-conversion.properties:
company.id = com.struts2.convertors.MyCustomConverter
company.??????.id = com.struts2.convertors.MyCustomConverter
MyAction:
public class MyAction extends ActionSupport {
private Company company;
public Company getCompany () {
return company;
}
public void setCompany (Company company) {
this.company= company;
}
#Override
public String execute() {
return SUCCESS;
}
}
Company:
public class Company {
private ID id;
private List<Employee> employees;
// getters and setters
}
Employee
public class Employee{
private ID id;
private String name;
// getters and setters
}
TypeConversion Annotation:
This annotation is used for class and application wide conversion rules.
The TypeConversion annotation can be applied at property and method level.
#TypeConversion(converter = “com.test.struts2.MyConverter”)
public void setAmount(String amount)
{
this.amount = amount;
}
This annotation specifies the location of one of my converters. literally, by using this annotation, I register my class com.test.struts2.MyConverter as a converter, and gets executed every time when setAmount(String amount) method is invoked.
Try the following by adding a converter for the ID type to the xwork-conversion.properties file
com.struts2.ID = com.struts2.convertors.MyCustomConverter