Jackson Deserialization Fails because of non-default constructor created by lombok - java

Jackson can deserialize json for the following class in 2.6.5 but fails in 2.8.8.
Model:
public static class Parent {
public long id;
public List<Child> children;
}
#RequiredArgsConstructor
public static class Child {
public long childId;
#NonNull
#JsonIgnore
public Parent parent;
public Child() { }
}
JSON:
{
"id": 1,
"children": [
{
"childId": 2
}
]
}
The exception is:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "childId" (class Parent), not marked as ignorable (2 known properties: "children", "id"])
I have found that the Child constructor created by lombok is causing this error. When I get rid of the lombok annotation or if I create the constructor manually, this stops happening. Either way, it should be using the no-args Child() constructor. What is causing this issue?

Lombok adds the annotation #ConstructorProperties({"parent"}) to the generated constructor. In Jackson 2.8.8, this causes the constructor to be treated as a "delegate creator".
A delegate creator allows Jackson to deserialize json for one type of object into another type of Java object.
In this case, because lombok generates the constructor #ConstructorProperties({"parent"}) Child(Parent parent) {...} Jackson will try to deserialize the child json as a Parent object, which could then be passed into the constructor to create a Child. It then throws the exception because childId is not a field in Parent.
One workaround is to configure the ObjectMapper used to deserialize the JSON with a custom JacksonAnnotationIntrospector so it won't interpret the constructor as a delegate creator.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector().setConstructorPropertiesImpliesCreator(false));
Update
Version 1.16.20 of project lombok did indeed default lombok.anyConstructor.suppressConstructorProperties to true as Roel indicated might happen in his comment.
That makes upgrading lombok to the latest version another fix for this issue.

Related

Jackson deserialization of a Lombok #Builder class using a mixin is not working

This is my class:
#Builder
#Value
public class A {
int id;
String name;
#NonNull String lastName;
}
The Lombok #Builder will add the all args constructor.
I need to deserialise a string into a POJO object.
I created the following Jackson mixin containing all three properties:
public abstract class AMixin {
public AMixin(#JsonProperty("name") String name,
#JsonProperty("id") int id,
#JsonProperty("lastName") String lastName) {
}
#JsonProperty("name")
abstract String getName();
#JsonProperty("id")
abstract int getId();
#JsonProperty("lastName")
abstract String getLastName();
}
I deserialise like this:
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(A.class, AMixin.class);
String ss = "{\"id\":1,\"name\":\"some name\",\"lastName\":\"some name\"}\n";
A c = mapper.readValue(ss, A.class);
}
but I get this error:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.bla.test.A` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"id":1,"name":"some name","lastName":"some name"}
"; line: 1, column: 2]
I found the answer.
Add lombok.config file with content:
lombok.anyConstructor.addConstructorProperties=true
The issue here is that Jackson expects a no-argument constructor or some other configured way of creating the object.
As of Lombok v1.18.14, the #Jacksonized annotation can be added to the class with the #Builder annotation to automatically configure the builder to be used for Jackson deserialization.
#Jacksonized
#Builder
#Value
public class A {
int id;
String name;
#NonNull String lastName;
}
The Lombok documentation for #Jacksonized describes this annotation in more detail:
The #Jacksonized annotation is an add-on annotation for #Builder and #SuperBuilder. It automatically configures the generated builder class to be used by Jackson's deserialization. It only has an effect if present at a context where there is also a #Builder or a #SuperBuilder; a warning is emitted otherwise.
[...]
In particular, the annotation does the following:
Configure Jackson to use the builder for deserialization using #JsonDeserialize(builder=_Foobar_._Foobar_Builder[Impl].class)) on the class (where Foobar is the name of the annotated class, and Impl is added for #SuperBuilder). (An error is emitted if such an annotation already exists.)
Copy Jackson-related configuration annotations (like #JsonIgnoreProperties) from the class to the builder class. This is necessary so that Jackson recognizes them when using the builder.
Insert #JsonPOJOBuilder(withPrefix="") on the generated builder class to override Jackson's default prefix "with". If you configured a different prefix in lombok using setterPrefix, this value is used. If you changed the name of the build() method using using buildMethodName, this is also made known to Jackson.
For #SuperBuilder, make the builder implementation class package-private.
Note: This issue has nothing to do with the usage of a mixin, which can be verified by moving Jackson configuration from the mixin to the class itself and observing that the issue is still present.

How to remove duplicate #JsonProperty in Jackson

I have a very simple JSON class with Jackson:
public class Hoge {
#JsonProperty("id")
public final int cid;
public Hoge(#JsonProperty("id") aid){
cid = aid;
}
}
I noticed that I have to put #JsonProperty("id") in two places to convert mutually JSON string <-> JSON object,
The first place is the field declaration and the second one is the argument of the constructor.
When I removed the first #JsonProperty, the JSON key becomes "cid", not "id". But I want the key name being "id".
When I removed the second one, I have the exception:
No suitable constructor found for type [simple type, class Series]: can not instantiate from JSON object (need to add/enable type information?)
I want to keep the name of field (The "cid" in the code) and the JSON key (The "id" in the code) are different.
When I removed the constructor, I have a compile error because the id have to be "final" to keep it immutable.
Does anyone knows how to remove the duplicate #JsonProperty in the example?
If you really wants to keep the final modifier in the field and remove the #JsonProperty annotation from the constructor, you could annotate the constructor with #JsonCreator and rename the parameter from aid to id, as shown below:
#Data
public class Hoge {
#JsonProperty("id")
public final int cid;
#JsonCreator
public Hoge(int id){
this.cid = id;
}
}
Then use one of the following approaches:
ParanamerModule
Register the ParanamerModule in your ObjectMappper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParanamerModule());
You'll need the following dependency on the classpath:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-paranamer</artifactId>
<version>${jackson.version}</version>
</dependency>
Refer to the documentation for details.
ParameterNamesModule
Register the ParameterNamesModule module and ensure the code is compiled using the -parameters option:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
The following dependency is required:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>${jackson.version}</version>
</dependency>
See the documentation for details.

Issue with deserializing JSON using Jackson

I have such JSON
{"body":{"result":[{"crossStateId":1,"raceId":181564,"withOfficer":1,"documents":[{"indexed":0,"documentNumber":"zzz","isMain":1,"documentTypeId":6,"serverId":16,"countryId":327,"useDate":"2017-02-07T19:31:51.000+0000","documentSubTypeId":6,"crossId":5018177,"documentId":44973231,"personId":222,"infinity":0,"documentValid":"2023-08-25T20:00:00.000+0000"}],"directionId":2,"documentNumber":"sss","operatorUsername":"AIRPORT_84","crossDate":"2017-02-07T19:31:51.000+0000","serverId":16,"crossTypeId":1,"crossRegisterDate":"2017-02-07T19:31:52.818+0000","officerNote":"","children":[],"personNote":"","crossId":5018177,"workplaceId":82,"divisionId":2,"race":{"carriageContainer":0,"raceId":181564,"raceStateId":1,"directionId":2,"creatorId":415,"countryId":327,"transportIdByType":605,"raceDateTime":"2017-02-07T19:20:58.000+0000","raceNumber":"841 sss sss","creatorUsername":"AIRPORT_8","divisionId":2,"transportTypeId":3,"createDate":"2017-02-07T19:20:58.000+0000"},"syncState":0,"autos":[],"userId":491,"raceNumber":"841 sss sss","operatorNote":"","person":{"firstNameEn":"JUMBERI","indexed":1,"lastNameGe":"ჩოხელი","genderId":2,"personId":6027803,"personalNumber":"222","countryNameGe":"sss","birthDate":"1963-06-14T20:00:00.000+0000","lastNameEn":"sss","countryId":327,"firstNameGe":"sss"},"airplane":{"raceNumber":"841 sss sss","airCompanyId":1,"airplaneId":605,"airportId":5657,"bortNumber":"01","transportSubTypeId":78,"countryId":360},"underAge":0,"personId":6027803,"decisionId":22}],"total":8264},"errorCode":0}
I would like to deserialize it to Java class but I am interested in only some JSON fields. Anyway here are the model classes:
public class Response implements Serializable {
private Body body;
private long errorCode;
}
public class Body implements Serializable {
Result result[];
}
public class Result implements Serializable {
private long crossStateId;
private long raceId;
private Person person;
private Child children [];
private Auto autos[];
}
etc.
But for some reason I get following exception:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "body" (Class com.demo.Response), not marked as
ignorable at [Source: java.io.StringReader#6483f5ae; line: 1, column:
10] (through reference chain: com.demo.Response["body"])
Here is code(the JSON string is correctly received and has same format as I initially mentioned in the beginning):
String res = MainProgram.sendGet("someURL");
ObjectMapper objectMapper = new ObjectMapper();
Response ob = objectMapper.readValue(res, Response.class);
I would appreciate some help.
You need to create getters and setters for the fields, and you should add annotations to your fields.
Annotation:
#JsonProperty(value = "body")
private Body body;
Doing one of above will make it work.
Sidenote:
You can create your pojos from json automatically with http://www.jsonschema2pojo.org/. Just paste it in and download it, or use one of their plugins.
As mentioned by others, private fields are not auto-detect by default, so either:
Annotating fields with #JsonProperty OR
Adding setter
is needed for deserialization.
However, there is another possibility: you can use annotations #JsonAutoDetect to change minimum visibility needed, and here enable discovery of ALL fields.
Or you can even change the defaults used via ObjectMapper method (something like setVisibility(...)).

JSON: UnrecognizedPropertyException for List of child objects

It seems to be a straightforward implementation but somehow not working for me.
public class ParentEntity {
private List<ChildEntity> childFields;
public List<ChildEntity> getChildFields() {
return childFields;
}
public void setChildFields(List<ChildEntity> childFields) {
this.childFields = childFields;
}
}
Input JSON
{
"childFields": [
{<different child properties>},
{<different child properties>}
]
}
Exception
class ChildEntity not marked as ignorable (11 known properties:...different child field properties
Regarding the exception message that you added, you have a mismatch in the properties you specified in your JSON for the ChildEntity and the ChildEntity properties.
If you have a mismatch and you want to specify more properties in JSON, than available in the ChildEntity class, you can use Jackson's
#JsonIgnoreProperties
annotation. It will ignore every property you haven't defined in your POJO.
You could also choose to use:
ObjectMapper objectMapper = getObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
It will ignore all the properties that are not declared.

JsonMappingException using Jackson - mistakenly places a field in the wrong class

so I am getting a JsonMappingException with what I consider to be a weird error:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "CoTravellers" (Class JobPush), not marked as
ignorable at [Source: java.io.StringReader#416a60a0; line: 1, column:
947] (through reference chain: >JobPush["CoTravellers"])
Now, what I have is this:
ProtocolContainer --> JobPush (inherits from DataPacket) --> Job --> CoTravellers
The JobPush, mentioned in the error above, is a sub-class of DataPacket. So, the ProtocolContainer has one DataPacket, and I have several classes inheriting DataPacket, where JobPush is one.
The JobPush is simple, looks like this:
public class JobPush extends DataPacket
{
public Job Job;
}
and it is in the Job-class that the CoTravellers field exists, not in the JobPush:
public class Job implements Serializable
{
#JsonDeserialize(using=CustomMapCoTravellerDeserializer.class)
public Map<Objects.CoTravellers, Integer> CoTravellers;
// ....
}
As you can see, I am trying to use a custom deserializer (see here for reference).
Now, I cannot understand why I get an error saying that there is no field "CoTravellers" in JobPush? I never said that CoTravellers is in the JobPush, as it is inside the Job-class.
The JSON I am parsing looks like this (this is cropped a bit for clarity, where SubPacket is the variable name, holding the DataPacket which in this case is a JobPush):
"SubPacket":{
"__type":"JobPush:#DataPackets.ToVehicle",
"Job":{
"CoTravellers":[
{
"Key":{
"CoTravellerId":0,
"Name":"Medresenär"
},
"Value":1
}
]
}
}
Anyone out there who can clue me in? =)
---- EDIT 1 ----
Adding some stuff, for clairity:
So, classes inherit from the class "DataPacket", and thus I have used #JsonSubTypes annotation to deal with that. This is the DataPacket-class:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="__type")
#JsonSubTypes(
{
#JsonSubTypes.Type(value = JobPush.class, name = "JobPush:#DataPackets.ToVehicle"),
})
public class DataPacket
{
}
you talk about a CoTraveller field, but in the JSON it is CoTravellers with an 's' on the end.

Categories

Resources