Gson POJO mapping loses custom field value - java

I'm trying to use Gson to map JSON to POJO where the POJO contains a custom field that is not part of JSON. The field gets updated when the setters of other fields are invoked to add the names of the fields that are being updated to a list.
The POJO class looks something like this:
public class myPojo {
private List<String> dirtyFields;
private String id;
private String subject;
private String title;
public myPojo() {
dirtyFields = new ArrayList<String>();
}
public getId() {
return id;
}
public setId(String id) {
this.id = id;
}
public getSubject() {
return subject;
}
public setSubject(String subject) {
this.subject = subject;
dirtyFields.add("subject");
}
// more setter/getters
}
The dirtyFields ivar is not a deserialized field but it is used to keep track of the fields that are being updated.
After mapping, however, the list seems to become an empty list. This was not the case with Jackson.
Is this due to the expected Gson behaviour?

Gson does not call setter/getters during deserialization/serialization. It access, instead, directly to fields (even if private/protected) using reflection. This explains why your dirtyFields ivar is empty.
The possibility of calling setter/getters is not implemented in Gson as far as I know. The reason why Gson acts like this is explained better here. A comparison between Jackson and Gson features can be found here, you may be interested in setter/getter part.
However Gson is quite flexible to add a custom behavior to get what you need, you should start reading Read and write Json properties using methods (ie. getters & setters) bug
Another way to calculate your dirtyFields list could be using reflection and checking if every field of your POJO is null or not. You could start from this.

Related

Spring Boot parse JSON data to Java Class with different field names

I am new to Spring Boot and I am trying to figure out how to parse json data. I see a lot of tutorials on how to map json string object to an annotated Java class and using and object mapper, like this:
json:
{
"UUID": "xyz",
"name": "some name"
}
public class MyClass{
#JsonProperty
private UUID id;
#JsonProperty
private String name;
#JsonAnyGetter
public UUID getId() {
return this.id;
}
#JsonAnySetter
public void setId(UUID id) {
this.id = id;
}
#JsonAnyGetter
public String getName() {
return this.name;
}
#JsonAnySetter
public void setName(String name) {
this.name = name;
}
}
ObjectMapper objectMapper = new ObjectMapper();
MyClass customer = objectMapper.readValue(jsonString, MyClass.class);
The problem is that the system I am getting the json string from does not match the class naming conventions we use (and I cannot change either one). So, instead of having the example json string above, it might look like this:
{
"randomdstring-fieldId": "xyz",
"anotherrandomstring-name": "some name"
}
This use case only has two fields, but my use case has a larger payload. Is there a way to either map the field names from the json object to the field names in the Java class or is there a way to just parse the json string as a key value pair (so that I can just manually add the fields to my Java object)?
In Jackson with #JsonProperty you can customize the field name with it's annotation parameter value
Therefore, you just have to annotate the entity fields with the #JsonProperty annotation and provide a custom JSON property name, like this:
public class MyClass{
#JsonProperty("original_field_name_in_json")
private UUID id;
...
The #JsonProperty will do it for you:
#JsonProperty("name_in_json")
private Long value;

How to move several params to another node in jackson?

I have to generate strange json from object(with jackson ObjectMapper), something like:
{
"data":{
"id":"1",
"name":"Json"
},
"userType":"MD"
}
But, with jackson data-bind i can do only:
{
"id":"1",
"name":"Json",
"userType":"MD"
}
With User.class:
public class User {
private String id;
private String name;
private String userType;
//Getters Setters Constructor
I found two ways of bypassing. First one - to use another "superclass":
public class Data {
#JsonProperty("data")
private User user;
private String userType;
//Getters Setters Constructor
Second way - to use Map:
Map<String, Object> map = new HashMap<>();
map.put("data", user);
map.put("userType", "MD");
String json = objMapper.writeValueAsString(map);
But i think, that it's not idea to use this ways if i have about 8 different objects with such structure. So, what's best pratice? Or maybe there are another built in ways?
I want to find the best one
The first approach you mentioned is the best practice, but it is called Composition (HAS-A relationship) rather 'superclass' and just to add readability, you can refactor your classes like this:
public class User {
private Data data;
private String userType;
//Getters Setters Constructor
}
public class Data {
private String id;
private String name;
// getters and setters
}
So, when you marshal your pojo to json, structure will be:
{
"data":{
"id":"1",
"name":"Json"
},
"userType":"MD"
}
which is what you want! Happy coding :)
Update: If you are generating your API documentation with Swagger or Open API Spec, then there is no way to have a representation of your model if you use Map or HashMap. So, obviously, the first approach is the one you should consider!

Questions on Gson and Java model class

I am using Gson for converting between json and java object.
Let's say the json is like this:
{
"name": "John",
"age": 12,
"adult": false
}
The class for the json is:
public class Student {
#Expose
#SerializedName("name")
private String name;
private int age;
private boolean adult;
// setters for all fields above
public void setName(String name) {
this.name = name;
}
...
// getters for all fields above
public String getName() {
return name;
}
...
}
My questions are:
Is it so that all fields showing in json should have #Expose annotation? Does that also mean we can have other fields which are not part of the json string?
Is it so that only if the field name in json and the variable name in java class is different, then use #SerializedName annotation is needed, otherwise it is optional?
Are setter functions necessary in java class for the fields?
The short answers to your questions are 1. yes, 2. yes, 3. no.
Gson has a lot going on under the hood - your Student class needs very little for it to do its work:
data members which match the key values in the JSON (case-sensitive!)
public getter methods
You can have more data members that don't necessarily match the key names in the JSON, they'll just end up getting set to null when Gson processes the data. Gson also does not require any annotations so long as the variable names match the key names in the JSON. You are allowed to have any kind of setter methods if you need them for other functionality (Gson does not). Basically, the only thing that Gson is looking for is data members that match the keys, and the getter methods. Whether your class is more complex is up to you and what your application needs.
edit: All of the values that Gson processes will be of the String type. If you have int and boolean types you will need to process the input as a String and operate on it to convert it to what you are looking for.

How can I avoid duplicate #JsonProperty annotations with an immutable object and non-matching property name?

The json I'm dealing with uses underscores in the property names, but I wish to keep camel case in Java. Further, I'm using immutable style POJOs, since that's a best practice our team has long adopted.
Everything works fine if I put duplicate #JsonProperty annotations in the constructor and on the getter, but this adds a lot of unnecessary bloat (in our classes, we have a couple dozen properties.) Is there a way to tell Jackson exactly once how to transform the Java property name to the JSON property name?
public class Foo {
public final String someProperty;
#JsonCreator
public Foo(#JsonProperty("some_property") someProperty) {
this.someProperty = someProperty;
}
#JsonProperty("some_property")
public String getSomeProperty() {
return someProperty;
}
}
You can choose the naming convention used for JSON. In this case you need SNAKE_CASE. It will convert someProperty field to "some_property": "" JSON. Then you don't need the #JsonProperty in the property.
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
In this case, in Foo, you need to provide the field in the constructor, what requires the #JsonProperty in the constructor params:
public class Foo {
public final String someProperty;
#JsonCreator
public Foo(#JsonProperty("some_property") String someProperty) {
this.someProperty = someProperty;
}
public String getSomeProperty() {
return someProperty;
}
}
At least you can get rid of one of the annotations.

Using Spring #RequestBody to convert JSON with mixed convention in payload to Entity Object

I am using Spring #RequestBody to map a JSON payload to a Java Object. Unfortunately this JSON payload does not use a set convention but rather has names that use both camelCase and snake_case.
To be clear my Controller looks like this:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
... code here ...
}
with the MobileDeviceData Entity object having several setter methods like:
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setFlagId(int flagId) {
this.flagId = flagId;
}
This works great and without any extra effort when the JSON objects name is camelCase. However for snake_case names I need to add the Annotation:
#JsonProperty("flag_id")
private int flagId;
in order for it to be picked up.
I know it's not a good idea to use the #JsonProperty if it can be avoided as you then will need to annotate every parameter. My question is, is there a more general way to enforce matching snake_case with the corresponding camelCase in the Entity object? And obviously to do it without screwing up the ones that are already camelCase.
As per the article here, there is a simple approach to deserialize the MobileDeviceData class. Here is the sample code as below:
#JsonDeserialize(using = UserDeserializer.class)
public class User {
private ObjectId id;
private String username;
private String password;
public User(ObjectId id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public ObjectId getId() { return id; }
public String getUsername() { return username; }
public String getPassword() { return password; }
}
Assume User is the class we’re interested in writing the Deserializer for. Not much is notable here, except for the annotations that tell Jackson who knows how deserialize this class.
public class UserDeserializer extends JsonDeserializer {
#Override
public User deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
return new User(null,
node.get("username").getTextValue(),
node.get("password").getTextValue());
}
}
The deserializer is created by extending Jackson’s abstract JsonDeserializer class, and giving it the type we want to deserialize to. Difficult is figuring out that you can reference the JSON by field name with the JsonParser's ObjectCodec.
I hope it helps.
Please feel free to comment if needed!
Having been working on this a bit, I now realize doing anything like what was requested would be counterproductive.
When you receive (deserialize) a JSON Object, it is generally expected that you will deliver (serialize) with the same parameters. If an implementation extracted both camelCase and underscore parameters the same way, then it would not know how to deserialize correctly later on. By following a standard convention and then using #JsonProperty for all the exceptions, it remains possible to deserialize and later deliver the JSON object just as it was received.

Categories