Jackson unable to deserialize immutable object with enum field - java

Spring Boot 2.5.4 with Jackson 2.12.4
Given the following simplified enum...
#AllArgsConstructor
#Getter
public enum PaymentMethod {
CREDITCARD(1);
private long id;
}
... and a request object which shall be deserialized using Jackson:
#NoArgsConstructor
#Getter
#Setter
public class PaymentRequest {
#JsonProperty(value = "paymentMethod")
private PaymentMethod paymentMethod;
}
This works just fine. Now, I'd like to make the request object immutable, so I changed it to this:
#RequiredArgsConstructor
#Getter
public class PaymentRequest {
#JsonProperty(value = "paymentMethod")
private final PaymentMethod paymentMethod;
}
But this variant fails:
Cannot construct instance of 'PaymentRequest' (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Is this some Jackson limitation when deserializing enums?

This issue not because parameter is an Enum, its because Lombok and Jackson doesn't work together in this scenario.
When deserializing to an immutable class, we need to explicitly mention that with Jackson annotation. Usually we annotate the constructor. But in this case the constructor is created by Lombok and Lombok do not add those annotation.
The easy fix is, remove #RequiredArgsConstructor annotation and create constructor yourself. Then annotate constructor as #JsonCreator. Something like this,
#Getter
public class PaymentRequest {
#JsonProperty(value = "paymentMethod")
private final PaymentMethod paymentMethod;
#JsonCreator
public PaymentRequest(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
}

Related

Deserializing abstract generic class in Java with Jackson

I am sending a POST request with the Sample JSON request body:
"name" : "jeff",
"country" : "US",
"content" : {
"subject" : "Test-Subject",
"body" : "Test-body"
}
The class that is this JSON is deserialized into:
#Introspected
#Builder
#Data
public class Template<T extends Content> {
String name;
String country;
T content;
}
Content looks like this:
#Introspected
#Superbuilder
#Getter
#EqualsAndHashCode
#NoArgsConstructor
#AllArgsConstructor(onConstructor_ = #JsonCreator)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property="content")
#JsonSubTypes(#JsonSubTypes.Type(value = EmailContent.class, name="EmailContent"))
public abstract class Content {
private String body;
}
This is what I want T content to deserialize into:
#Introspected
#Superbuilder
#Getter
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor(onConstructor_ = #JsonCreator)
public class EmailContent extends Content {
private String subject;
}
I have everything working in Template EXCEPT the generic content type which is giving me trouble no matter what JsonTypeInfo I use. I am trying to deserialize it into an EmailTemplate class. I have other classes extending from content so I am not looking to use #JsonDeserialize.
Solved...it was due to using graalVM which supports partial reflection. I fixed this by adding the #ReflectiveAccess annotation alongside lombok's #Jacksonized annotation for deserializing builder types.

#Accessors(fluent = true) doesnot work with Jakson

In Spring boot application with Lombok, I have pojo class AccountDTO
#Data
#Builder
#Accessors(fluent = true)
public class AccountDTO implements Serializable {
private String identification;
}
My project compiles fine. However, it throws an exception in its execution
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No
serializer found for class AccountDTO and no properties discovered to create
BeanSerializer
if I removed the annotation #Accessors(fluent = true), then it will work fine without any problems.
How can i make Lombok #Accessors(fluent = true) and Jackson work together ?
I stumbled on this question recently while encountering the same problem and the solution for me was to add and explicit #JsonProperty annotation on the getter generated by Lombok using:
#Getter(onMethod = #__(#JsonProperty)).
In your case, the class would look like this:
#Data
#Builder
#Accessors(fluent = true)
#Getter(onMethod = #__(#JsonProperty))
public class AccountDTO implements Serializable {
private String identification;
}

Builders on abstract classes cannot be #Jacksonized (the builder would never be used)

I have two classes OAuth2Token and CachedOAuth2Token that extends a class called AbstractOAuth2Token.
AbstractOAuth2Token.java
#SuperBuilder
#Jacksonized
#JsonSubTypes({
#JsonSubTypes.Type(value = OAuth2Token.class),
})
#Getter
#Setter
#ToString
public abstract class AbstractOAuth2Token {
#JsonProperty("access_token")
private String accessToken;
#JsonProperty("token_type")
private String tokenType;
}
OAuth2Token.java
#Getter
#Setter
#SuperBuilder
#ToString(callSuper = true)
#JsonTypeName("OAuth2Token")
#Jacksonized
public class OAuth2Token extends AbstractOAuth2Token {
#JsonProperty("expires_in")
private int expiresIn;
}
CachedOAuth2Token.java
#Getter
#Setter
#SuperBuilder
#ToString(callSuper = true)
public class CachedOAuth2Token extends AbstractOAuth2Token {
private LocalDateTime expirationDate;
}
Unfortunately my Maven project doesn't build because AbstractOAuth2Token.java: Builders on abstract classes cannot be #Jacksonized (the builder would never be used).
Even if the code works as expected if the AbstractOAuth2Token isn't abstract, then I'm able to create an instance of it using the builder which indeed isn't what I want. Its constructor is protected, so no problem there.
The idea is that I want AbstractOAuth2Token to be abstract without losing any fuctionality in the children. I'm a fan of Lombok, so I want to be able to use the autogenerated builders but together with Jackson.
It's a Wildfly 11 project with Lombok 1.18.16
How can I solve this issue?
Don't add #Jacksonized to your abstract base class. Non-#Jacksonized #SuperBuilders are compatible with #Jacksonized #SuperBuilders. As Jackson will never use AbstractOAuth2Token's builder directly, there is no need to configure it for Jackson explicitly.

Spring Boot + Jackson >= 2.7.x fail with #JsonProperty(access = Access.READ_ONLY)

I have the following class:
public class Person {
#JsonProperty(access = Access.READ_ONLY)
private String id;
#JsonProperty(access = Access.READ_WRITE)
private String name;
// constructors and getters/setters are omitted
}
When I use it like this:
#RestController
public class PersonEndpoint {
#PostMapping("/")
public Person savePerson(#RequestBody Person person) {
System.out.println(person);
return person;
}
}
Jackson fails when deserializing the object:
WARN MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.mycompany.myapp.model.Person]]: com.fasterxml.jackson.databind.JsonMappingException: Could not find creator property with name 'name' (in class com.mycompany.myapp.model.Person)
There is no such problem when changing Jackson's version to 2.6.7.
Is this a bug or am I doing something wrong?

Explicit constructor using Lombok?

I'm rewriting some messy code that manages a database, and saw that the original programmer created a class mapped to the database like so:
(I've removed unnecessary code that has no purpose in this question)
#Entity
#Data
#EqualsAndHashCode(callSuper = false, of = { "accessionCode", "header", "date" })
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
protected PDBEntry(){}
public PDBEntry(String accessionCode, String header, Date date){
this.accessionCode = accessionCode;
this.header = header;
this.date = date;
}
}
I am still a beginner at Hibernate and using Lombok, but wouldn't this do the same thing and wouldn't Lombok automatically create the needed constructor for you?
#Entity
#Data
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#NonNull
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
#NonNull
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
}
Also, the original programmer of this code says he allows for the header to be 'null', yet he explicitly created a constructor that needs a value for header. Am I missing something or is this a bit contradictory?
Have a look at #NoArgsConstructor, #RequiredArgsConstructor, #AllArgsConstructor.
The constructor behavior of #Data is like #RequiredArgsConstructor:
#RequiredArgsConstructor generates a
constructor with 1 parameter for each
field that requires special handling.
All final fields get a parameter, as
well as any fields that are marked as
#NonNull that aren't initialized where
they are declared.
Given that none of your fields are either final or #NonNull, this will result in a no-argument constructor. However, this is not the most expressive way to achieve this behavior.
What you'll probably want in this case is a #NoArgsConstructor (optionally combined with a #AllArgsConstructor), to clearly communicate the intended behavior, as is also indicated in the documentation:
Certain java constructs, such as
hibernate and the Service Provider
Interface require a no-args
constructor. This annotation is useful
primarily in combination with either
#Data or one of the other constructor
generating annotations.
That bit is contradictory you're right. I've not used Lombok before but with hibernate if you want to be able to create a bean and persist you need the default constructor as given above as far I was aware. It uses Constructor.newInstance() to instantiate new objects.
Here is some hibernate documentation which goes into more detail.
Hibernate Documentation
If you are using #Data with a #NonNull field and still want a noargs-constructor, you might wanna try to add all 3 annotation together
#NoArgsConstructor
#RequiredArgsConstructor
#AllArgsConstructor
Apparently an old intelliJ bug which I did replicate in Eclipse Kepler and lombok v0.11.4
#NoArgsConstructor,
#RequiredArgsConstructor,
#AllArgsConstructor
Generate constructors that take no arguments, one argument per final / non-null field, or one argument for every field. Read this lombok-project
#Data
#RequiredArgsConstructor /*Duplicate method Someclass() in type Someclass*/
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true) /*Duplicate method Someclass() in type Someclass*/
#Entity
public class Someclass {
#Id
private String id;
private String name;
private Type type;
public static enum Type { X , Y, Z}
}
Fixed it by making member variables final
#Data
#RequiredArgsConstructor
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
#Entity
public class Someclass {
#Id
private final String id;
private final String name;
private final Type type;
public static enum Type { X , Y, Z}
}

Categories

Resources