I'm using Jackson JSON library to convert some JSON objects to POJO classes. The problem is, when I use JSON Objects with duplicated properties like:
{
"name":"xiaopang",
"email":"xiaopang1#123.com",
"email":"xiaopang2#123.com"
}
Jackson report the last email pair "email":"xiaopang2#123.com" and then parse the object.
I've learned from Does JSON syntax allow duplicate keys in an object? that what happens when deserializing a JSON object with duplicate properties depends on the library implementation, either throwing an error or using the last one for duplicate key.
Despite overheads of tracking all properties, is there any way to tell Jackson to report an error or exception such as "Duplicate key" in this case?
Use JsonParser.Feature.STRICT_DUPLICATE_DETECTION
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
MyPOJO result = mapper.readValue(json, MyPOJO.class);
Results in:
Exception in thread "main" com.fasterxml.jackson.core.JsonParseException: Duplicate field 'email'
You can also try to use DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY (more info) It will be trigggered if you deserialize your json string/input to jackson json tree first and then to you POJO. Can combine it with custom JsonDeserializer like this:
private static class MyPojoDeserializer extends JsonDeserializer<MyPOJO>{
#Override
public MyPOJO deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException{
JsonNode tree = p.readValueAsTree();
return p.getCodec().treeToValue(tree, MyPOJO.class);
}
}
Setup it once and use it same way as before:
// setup ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
SimpleModule module = new SimpleModule();
module.addDeserializer(MyPOJO.class,new MyPojoDeserializer() );
mapper.registerModule(module);
// use
mapper.readValue(json, MyPOJO.class);
Result:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Duplicate field 'email' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled
Other options would be to implement all the logic yourself in custom deserializer or in you POJO setter methods.
Related
#Entity
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class SomeRandomEntity {
private String var1;
private String var2;
private String var3;
public JSONObject getJSONObject throws JSONException {
JSONObject properties = new JSONObject();
properties.put("var1", getVar1());
properties.put("var2", getVar2());
properties.put("var3", getVar3());
return properties;
}
}
This POJO object is being given to frontend in the form of json object. But while fetching the data in the frontend this error is appearing.
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.json.JSONObject]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.json.JSONObject and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.data.domain.PageImpl["content"]->java.util.Collections$UnmodifiableRandomAccessList[0]->com.artifact.group.SomRandomDTO["jsonobject"])] with root cause
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.json.JSONObject and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.data.domain.PageImpl["content"]->java.util.Collections$UnmodifiableRandomAccessList[0]->com.artifact.group.SomRandomDTO["jsonobject"])
The error goes away when I add #JsonIgnore in the getJSONObject. The getJSONObject method is being considered a getter method and jackson tries to serialize that too. I want to understand this behaviour of jackson and why #JsonIgnore is rectifying the error?
Here your object when you return it in response is getting Serialized to json string using ObjectMapper which is used in spring's MessageConverter(i.e Jackson2HttpMessageConverter) bean. Now the error is caused due to how ObjectMapper serializes your class. Your class has 4 filed, 3 of type String and 1 of type JSONObject. ObjectMapper when serializing fields, tries to find the corresponding serializer based on the field type. There are some out-of-the-box implementation of serializer for known type like String but for your custom type you either need to provide serializer to ObjectMapper bean via configuration of set property SerializationFeature.FAIL_ON_EMPTY_BEANS to false.
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
To verify this you can change the return type of method getJSONObject to String (as shown below) and your code will work.
public String getJSONObject throws JSONException {
JSONObject properties = new JSONObject();
properties.put("var1", getVar1());
properties.put("var2", getVar2());
properties.put("var3", getVar3());
return properties.toString();
}
I'm trying to parse some JSON using Jackson. Here is the JSON:
{
"data": {
"item1": "Hello",
"item2": "World"
}
}
I've seen implementations using Jackson where the data field is ignored/not read but the inner elements are still read and stored. I was wondering how this is achieved?
Cheers!
in your mapper configuration you can Unwrap the root element as follows
private ObjectMapper rootMapper()
{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
return mapper;
}
for more detail on this you can look jackson data binder topic
I have a generic Response Object that takes a status message and a payload object.
public class Response {
private String status;
private Object payload;
// getters / setters
}
The object can be any type, including lists or arrays. (Much like here: Jackson JSON List with Object Type).
In the case where the object is a List, I need a serializer and an annotation to tell Jackson about it:
#JsonSerialize(using = ModelSerializer.class)
private List<Model> models;
How can this be achieved in a generic way? I can introduce a common superclass to the object candidates but don't want to "implement" a seperate Response class for each type.
You can configure your object mapper by registering the serializer for List as described here : http://wiki.fasterxml.com/JacksonHowToCustomSerializers
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new ModelSerializer());
mapper.registerModule(testModule);
When serializing your Response, Jackson will use default serialization for all object except for the ones declaring a custom serializer.
I need to provide custom deserialization of the Map and then each Property object has to be serialized by default serializer. This map is part of another object:
class PropertiesHolder {
Map<String, Property> properties;
}
I've defined mixin for the PropertiesHolder class:
class PropertiesHolderMixIn {
#JsonSerialize(using=PropertiesSerializer.class)
#JsonDeserialize(using=PropertiesDeserializer.class)
Map<String, Property> properties;
}
I have also mixin for Property class. The ObjectMapper initialization:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setMixInAnnotation(Property.class, PropertyMixIn.class);
module.setMixInAnnotation(PropertiesHolder.class, PropertiesHolderMixIn.class);
mapper.registerModule(module);
My deserializer:
class PropertiesDeserializer extends JsonDeserializer<Map<String, Property>> {
public Map<String, Property> deserialize(JsonParser jp, DeserializationContext ctxt) throws ... {
ArrayNode node = (ArrayNode) jp.readValueAsTree();
for (int i = 0, size = node.size() ; i < size ; i++) {
ObjectNode jn = (ObjectNode) node.get(i);
String key = jn.get("propertyName").textValue();
String value = jn.get("propertyValue").toString();
... HERE I need to call registered deserializer for Property class over value ...
}
}
}
I've looked at How do I call the default deserializer from a custom deserializer in Jackson, but it doesn't work form me ... it ends with NPE. Also the solution described in the post creates deserializer for the outer class which for me is defined as mixin and I don't want to create deserializer for this class.
Please, point me to a solution. Where can I get default deserializer for the Property object?
Thanks
The problem is that you will need a fully constructed default deserializer; and this requires that one gets built, and then your deserializer gets access to it. DeserializationContext is not something you should either create or change; it will be provided by ObjectMapper
So all you need to write in the deserialize() method is:
ObjectMapper mapper = (ObjectMapper)jp.getCodec();
Property property = mapper.readValue(jn.get("propertyValue").toString(), Property.class));
Solution is this line of code:
ObjectMapper mapper = (ObjectMapper)jp.getCodec();
Call this method within "deserialize(...)" method. So the important (for me) code fragment is:
ObjectMapper mapper = (ObjectMapper)jp.getCodec();
Property property = mapper.readValue(jn.get("propertyValue").toString(), Property.class));
Found on this blog.
I have a POJO
class Product {
String name;
Size size;
}
So, I want to map a deserialize a JSON to my POJO. If I have both the attributes in my JSON, it is not a problem.
But in my case, sometimes size will not be a part of the JSON. There might be a third attribute 'type' based on which I will set my size. I do not want to include 'type' in my POJO. Are there any Jackson annotations which can do this?
write your custom Deserializers:
SimpleModule module =
new SimpleModule("ProductDeserializerModule",
new Version(1, 0, 0, null));
module.addDeserializer(Product.class, new ProductJsonDeserializer());
mapper = new ObjectMapper();
mapper.registerModule(module);
//...
class ProductJsonDeserializer extends JsonDeserializer<Product>
{
#Override
public Product deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
// handle here if exist a third attribute 'type' and create the product
}
}
More info here: http://wiki.fasterxml.com/JacksonHowToCustomDeserializers
Found a pretty simple solution for this!
When a JSON attribute is attempted to be mapped to my POJO's attribute, it just checks whether a setter exists for it.
For example, if there is an attribute type in JSON, it will try to hit a method named setType(obj) in my POJO, regardless of whether there exists an attribute named type.
This worked for me! I simply set my other attributes inside this setter.