How to remove duplicate #JsonProperty in Jackson - java

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.

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 create default constructor for immutable class

I like to make my objects immutable based on this article (Why objects must be immutable).
However, I am trying to parse an object using Jackson Object Mapper. I was initially getting JsonMappingException: No suitable constructor found for type [simple type, class ]: cannot instantiate from JSON object.
I could fix it as mentioned here, by providing a default constructor and making my fields non-final.
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
#AllArgsConstructor
// #NoArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
#Data
public class School {
#NonNull
private final String schoolId;
#NonNull
private final String schoolName;
}
What is a good programming style that I should follow to overcome this problem? Is the only way around is to make my objects mutable?
Can I use a different mapper that does not use the default constructor?
You can use a Jackson factory (method annotated with #JsonCreator) that reads fields off a map and calls your non-default constructor:
class School {
//fields
public School(String id, String name) {
this.schoolId = id;
this.schoolName = name;
}
#JsonCreator
public static School create(Map<String, Object> object) {
return new School((String) object.get("schoolId"),
(String) object.get("schoolName"));
}
//getters
}
Jackson will call the create method with a Map version of the json. And this effectively solves the problem.
I believe your question looks for a Jackson solution, rather than a new pattern/style.
TL;DR: using lombok and avoiding a default constructor
make immutable data class using #Value
annotate all your fields with #JsonProperty("name-of-property")
add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config to copy those to generated constructors
create an all-args constructor annotated with #JsonCreator
example:
#Value
#AllArgsConstructor(onConstructor_ = #JsonCreator)
class School {
#JsonProperty("schoolId")
String schoolId;
#JsonProperty("schoolName")
String schoolName;
}
long answer
There is an imo better alternative to a static factory method annotated with #JsonCreator, and that is having a constructor for all Elements (as is required for immutable classes anyway). Annotate that with #JsonCreator and also annotate all parameters with #JsonProperty like this:
class School {
//fields
#JsonCreator
public School(
#JsonProperty("id") String id,
#JsonProperty("name") String name) {
this.schoolId = id;
this.schoolName = name;
}
//getters
}
Those are the options the #JsonCreator annotation gives you. It describes them like this in its documentation:
Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to
You might not even need to explicitly specify the parameter name under some circumstances. The documentation regarding that for #JsonCreator further states:
Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode. But with JDK 8 (or using helper libraries such as Paranamer, or other JVM languages like Scala or Kotlin), specifying name is optional.
Alternatively this will also work nicely with lombok version 1.18.3 or up, where you can add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config and therefore have it copy the JsonProperty annotations to the constructor, given that you do annotate all fields with it (which one should do anyway imo). To put the #JsonCreator-annotation on the constructor, you can use the experimental onX feature. Using lombok's #Value for immutable data classes, your DTO then might just look like this (untested):
#Value
//#AllArgsConstructor(onConstructor = #__(#JsonCreator)) // JDK7 or below
#AllArgsConstructor(onConstructor_ = #JsonCreator) // starting from JDK8
class School {
#JsonProperty("schoolId")
String schoolId;
#JsonProperty("schoolName")
String schoolName;
}

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

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.

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

Unmarshalling json by reversing the affect of #XmlElement renaming?

I have a class defined as follows:
public class Contact implements Serializable
{
private final static long serialVersionUID = 1L;
#XmlElement(name = "last-name", required = true)
protected String lastName;
#XmlElement(name = "first-name", required = true)
protected String firstName;
#XmlElement(required = true)
protected String id;
#XmlElement(name = "primary-phone")
protected String primaryPhone;
#XmlElement(name = "cellular-phone")
protected String cellularPhone;
}
This class is being used to generate marshalled JSON versions which are communicated over the internet. On the receiving end I'm trying to unmarshall the JSON, but I'm having difficulty because of the difference in naming, i.e. for example the unmarshalling library expects a variable named primaryPhone, not primary-phone which is what I have at the receiving end.
Besides pre-processing the received JSON text to manually replace instances of primary-phone with primaryPhone, is there some other more automated way to avoid this problem ? The problem with manually converting strings is that tomorrow if the Class definition changes, the code I'm writing will also need to be updated.
Here's a code snippet showing what I'm currently doing without any manually string conversion:
String contact = "\"last-name\": \"ahmadka\"";
ObjectMapper objMapper = new ObjectMapper();
Contact cObj = objMapper.readValue(contact, Contact.class);
But with the above code I get an exception on the last line reading this:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "last-name" (class Contact), not marked as ignorable (5 known properties: "lastName", "cellularPhone", "id", "primaryPhone", "firstName", ])
at ...........//rest of the stack
Jackson by default doesn't know about JAXB annotations (i.e. #XmlRootElement). It needs to be configured with an external module in order to have this capability. On the server, you most likely have this without even knowing it.
On the client side if you want to configure the ObjectMapper, then you need to add the following module:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>${jackson2.version}</version>
</dependency>
Then just register the JAXB annotations module.
ObjectMapper objMapper = new ObjectMapper();
mapper.registerModule(new JaxbAnnotationModule());

Categories

Resources