Jackson : avoiding exceptions due to unmodeled fields - java

I have some beans, and they model (explicitly) the core data types in a JSon. However, sometimes the Jsons im reading have extra data in them.
Is there a way to annotate/define a Bean in jackson so that it uses explicit field names for some of the fields (the ones I know of, for example), while cramming the extra fields into a map / list ?

Yes there is, assuming you really do want to retain all the extra/unrecognized parameters, then do something like this:
public class MyBean {
private String field1;
private String field2;
private Integer field3;
private Map <String, Object> unknownParameters ;
public MyBean() {
super();
unknownParameters = new HashMap<String, Object>(16);
}
// Getters & Setters here
// Handle unknown deserialization parameters
#JsonAnySetter
protected void handleUnknown(String key, Object value) {
unknownParameters.put(key, value);
}
}
To configure global handling of parameters you can choose to define an implementation of DeserializationProblemHandler and register it globally with the ObjectMapper config.
DeserializationProblemHandler handler = new MyDeserializationProblemHandler();
ObjectMapper.getDeserializationConfig().addHandler(handler);
If you find you really do not care about the unknown parameters, then you can simply turn them off. On a per-class basis with the #JsonIgnoreProperties(ignoreUnknown = true), or globally by configuring ObjectMapper:
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false)

Related

Jackson deserialization behaviour for a getter without property

While using jackson to deserialize and serialize a java DTO came across a weird behaviour.
Consider this sample java dto,
public class TempClz {
private List<String> field1;
public List<String> getField1() {
return field1;
}
public void setField1(List<String> field1) {
this.field1 = field1;
}
public List<String> getNotAtAll() {
return field1;
}
}
Now on creating a object of this class with field1 set to some value and on serializing this via a standard jackson mapper the sample json string obtained was
{"field1":["123"],"notAtAll":["123"]}
This behaviour was weird and I did not get a direct explanation for this in docs, but that once a getter is made, the property is available for both serialization and deserialization. This created a property using the function name in the resultant json response.
But again on deserialization of this string to pojo, the notAtAll list got appended to field1 list, i.e. in the resultant java object, field1 had size of two with values ["123","123"] which just seems wrong. Fixed this behaviour using jsonIgnore on the getter but can someone please help explain this behaviour exactly and whether this is intended?
A Getter Makes a Non-Public Field Serializable and Deserializable. So, no doubt that it added notAtAll field in json for getNotAtAll method. When you de-serialize the same string it has the values from both the getters but both of them return the same field i.e. field1. And consequently the values are added to your list. You've rightly used #JsonIgnore to ignore this getter.

Jackson ObjectMapper returns null when Object variable is set to Private

I am given this escaped JSON
"{\"UniqueId\":[],\"CustomerOffers\":{},\"Success\":false,\"ErrorMessages\":[\"Test Message\"],\"ErrorType\":\"GeneralError\"}"
and I need to convert it to Java object using Jackson.
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'
I created the class:
public class Data {
private List<UUID> UniqueId;
private Map<Integer, List<Integer>> CustomerOffers;
private Boolean Success;
private List<String> ErrorMessages;
private String ErrorType;
// getter, setters
}
Then I created the method to convert it
public class Deserializing {
public void processing(String input) {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
String jsonInString = "\"{\"UniqueId\":[],\"CustomerOffers\":{},\"Success\":false,\"ErrorMessages\":[\"Test Message\"],\"ErrorType\":\"GeneralError\"}\"";
String newJSON = org.apache.commons.lang3.StringEscapeUtils.unescapeJava(jsonInString);
newJSON= newJSON.substring(1, jsonInString.length()-1);
try {
// JSON string to Java object
Data data = mapper.readValue(newJSON, Data.class);
System.out.println(data);
System.out.println("Get Success "+ data.getSuccess()); // return "false" if Data object is public ; null if private
System.out.println("Get UniqueID " + data.getUniqueId()); // return [] if Data object is public ; null if private
} catch (IOException e) {
e.printStackTrace();
}
}
}
Whichever variables in Data class that are set to public, then I will get the corresponding value when I call getters.
Whichever variables in Data class that are set to private, then I will get null when I call getters.
Getters and Setters are always public.
I am wondering, why ObjectMapper can't map the object if it is set to private? I could set it to public, but that is not best practice.
The issue is that Jackson will always assume setSuccess() & getSuccess() will be used for a success field, not Success. JSON field names starting with uppercase letters need to be supported by #JsonProperty. Java has a convention where class members always start with lowercase letters, you can realize that by using this annotation.
When you make fields private, you force Jackson to utilize setters, and the above conflict makes it impossible to properly deserialize the Data object.
Solution is to do;
public class Data {
#JsonProperty("UniqueId")
private List<UUID> uniqueId;
#JsonProperty("CustomerOffers")
private Map<Integer, List<Integer>> customerOffers;
#JsonProperty("Success")
private Boolean success;
#JsonProperty("ErrorMessages")
private List<String> errorMessages;
#JsonProperty("ErrorType")
private String errorType;
// getter&setters
}
Then you will see the values deserialized properly into a Java object;
Get success false
Get uniqueID []
By default, Jackson will only attempt to serialize public fields on the Data class (or whatever class you are trying to serialize/unserialize). However, you may configure ObjectMapper to allow it serialize all fields, regardless of visibility:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
Data data = mapper.readValue(newJSON, Data.class);
See here and here for more information.
In the JavaBeans specification (the universal guide for structuring Java class properties), properties are defined as starting with a lower-case letter (errorMessages); the accessors, because they have a prefix, capitalize it (getErrorMessages). Jackson uses the property names by default, so when you have a method getErrorMessages, it looks for a JSON property with the key errorMessages.
The best approach is to change your JSON; I've seen the styles errorMessages and error_messages but never ErrorMessages. If you can't do that, you can apply #JsonProperty to your properties to tell Jackson to use a different name in the JSON.
Also, another point to note : In this if class Data contains any constructor with arguments and doesn't contain an empty constructor. Then the object mapper will not be able to map the corresponding object. So in this case also it will return null.

JSON serialization of different attributes of a single entity for different requests using Jackson

There are several REST calls that require the same JSON entity with a different set of attributes. Example of the entity:
public class JsonEntity
{
public String id;
public String name;
public String type;
public String model;
}
JsonEntity is a part of the complex responses of different calls. The first call requires the whole JsonEntity without changes. Second call requires JsonEntity without type and model attributes. Thrid one requires JsonEntity without name attribute.
Is there any way to retrieve the same JSON entity with a particular set of attributes depending on the particular context (except separating JsonEntity) using Jackson?
I see 3 ways of doing this:
1. Use #JsonGetter
This annotation tells jackson to use a metho rather than a field for serialization.
Create 3 subclasses of JsonEntity, one for each response. Change JsonEntity and use #IgnoreField on every field, make them protected if possible. On each subclasses, create specific getter for the fields you need, example:
public class JsonEntitySecondCall extends JsonEntity
{
#JsonGetter("id")
public String getId(){
return id;
}
#JsonGetter("name")
public String getName(){
return name;
}
}
Also, create a clone/copy constructor for JsonEntity. For your second call, create a new JsonEntitySecondCall by cloning the original JsonEntity, and use it in your API. Because of the anotation, the created Object will only serialisze the given fields. I don't this you can just cast your object, because Jackson uses reflection.
2. Use #AnyGetter
the AnyGetter annotaiton allows you to define a map of what will be serialized:
private Map<String, Object> properties = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
Now you just need to tell your JsonEntity what properties it needs to return before each call (you could create 3 methods, one for each context, and use an enum to set which one must be used.).
3. Use #JsonInclude(Include.NON_NULL)
This annotation tells Jackson not to serialize a field if it is null. You can then clone your object and set null the fields you don't want to send. (this only works if you shouldn't send null elements to the API)
For more on Jackson annotations use this link.

Runtime annotations design and performance

I have a java api which performs an external resource lookup and then maps the values to a Pojo. To do this, the api needs the field names of the Pojo as string values, something like:
public <F> F populatePojoFields(String primaryField, String secondaryField);
This works fine, however passing the pojo field names as String to the api does not feel right. I was able to change this by writing marker annotations for the pojo, so now it is like
public class POJO {
#Primary //custom marker annotation
private int mojo;
#Secondary //custom marker annotation
private String jojo;
}
String primaryField = getFieldNameUsingReflection(Pojo.class, Primary.class)
String secondryField = getFieldNameUsingReflection(Pojo.class, Secondary.class)
Pojo pojo = populatePojoFields(primaryField, secondaryField);
This way I don't have to keep track of string values, I can just add marker annotations to the Pojo fields. This works fine, but I'm worried about performance. Is this a standard way to do things? as keeping hardcoded string values is more efficient than looking up the field names every time we need to call the api. Is there a better way to do this?
If you call getFieldNameUsingReflection often you can think to cache the result of this call.
You can use a singleton class with internal Map with a code like the following:
public class SingletonMapPrimarySecondary {
Map<Class, String> mapPrimary;
Map<Class, String> mapSecondary;
// TODO: Handle mapPrimary and mapSecondary creation and singleton pattern
public String getPrimary(Class clazz) {
String primary = mapPrimary.get(clazz);
if (primary == null) {
primary = getFieldNameUsingReflection(clazz, Primary.class);
mapPrimary.put(clazz, primary);
}
return primary;
}
public String getSecondary(Class clazz) {
// TODO: Similar to getPrimary
}
}

Want to hide some fields of an object that are being mapped to JSON by Jackson

I have a User class that I want to map to JSON using Jackson.
public class User {
private String name;
private int age;
private int securityCode;
// getters and setters
}
I map this to a JSON string using -
User user = getUserFromDatabase();
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
I don't want to map the securityCode variable. Is there any way of configuring the mapper so that it ignores this field?
I know I can write custom data mappers or use the Streaming API but I would like to know if it possible to do it through configuration?
You have two options:
Jackson works on setters-getters of fields. So, you can just remove getter of field which you want to omit in JSON. ( If you don't need getter at other place.)
Or, you can use the #JsonIgnore annotation of Jackson on getter method of that field and you see there in no such key-value pair in resulted JSON.
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
Adding this here because somebody else may search this again in future, like me. This Answer is an extension to the Accepted Answer
You have two options:
1. Jackson works on setters-getters of fields. So, you can just remove getter of field which you want to omit in JSON. ( If you don't need getter at other place.)
2. Or, you can use the `#JsonIgnore` [annotation of Jackson][1] on getter method of that field and you see there in no such key-value pair in resulted JSON.
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
Actually, newer version of Jackson added READ_ONLY and WRITE_ONLY annotation arguments for JsonProperty. So you could also do something like this.
#JsonProperty(access = Access.WRITE_ONLY)
private String securityCode;
instead of
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
you also can gather all properties on an annotation class
#JsonIgnoreProperties( { "applications" })
public MyClass ...
String applications;
If you don't want to put annotations on your Pojos you can also use Genson.
Here is how you can exclude a field with it without any annotations (you can also use annotations if you want, but you have the choice).
Genson genson = new Genson.Builder().exclude("securityCode", User.class).create();
// and then
String json = genson.serialize(user);
Field Level:
public class User {
private String name;
private int age;
#JsonIgnore
private int securityCode;
// getters and setters
}
Class Level:
#JsonIgnoreProperties(value = { "securityCode" })
public class User {
private String name;
private int age;
private int securityCode;
}
if you are using GSON you have to mark the field/member declarations as #Expose and use the GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()
Don't forget to mark your sub classes with #Expose otherwise the fields won't show.
I suggest you use this.
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private int securityCode;
This allows you to set the value of securityCode(especially if you use lombok #Setter) and also prevent the field from showing up in the GET request.
I had a similar case where I needed some property to be deserialized (JSON to Object) but not serialized (Object to JSON)
First i went for #JsonIgnore - it did prevent serialization of unwanted property, but failed to de-serialize it too. Trying value attribute didn't help either as it requires some condition.
Finally, working #JsonProperty with access attribute worked like a charm.

Categories

Resources