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
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.
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.
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(...)).
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.
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.