When creating a REST API servlet, Jackson provides a huge list of possibilities to manipulate the JSON data, mostly using annotations.
Let’s demonstrate some of them with this little class, which has only two properties:
public class Demo { private String foo;
private String bar;
public String getFoo() { return foo; } public void setFoo(String foo) { this.foo = foo; }
public String getBar() { return bar; } public void setBar(String bar) { this.bar = bar; }
}
The playground converts the content string to a POJO and back to a string:
String content = "{ \"foo\": \"bar\" }";
// init the ObjectMapper
ObjectMapper mapper = new ObjectMapper();
// build the Object
Demo test = null;
try {
test = mapper.readValue(content, Demo.class);
} catch (Exception e) {
e.printStackTrace();
}
// and now convert it back to a String
String data = null;
try {
data = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(test);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( data );
If we run this code, the result is not really spectacular:
{
"foo" : "bar",
"bar" : null
}
So let’s ignore the property foo by adding the annotation @JsonIgnoreProperties to the Demo class:
@JsonIgnoreProperties({"foo"})
public class Demo { ... }
Now, foo is no longer in our resulting JSON:
{
"bar" : null
}
The property bar is null, and we don’t like nulled properties in our JSON. That’s why we add another annotation, @JsonInclude:
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Demo { ... }
After removing the previously added @JsonIgnoreProperties annotation, our result looks like this (the empty property bar was skipped):
{
"foo" : "bar"
}
What happens if we change our content string, and add an unknown property?
String content = "{ \"foo\": \"bar\", \"undefined\": \"property\" }";
An error occurs because Jackson does not know how to handle the new property:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "undefined" (class ch.hasselba.JacksonPlayground.Demo), not marked as ignorable (2 known properties: "foo", "bar"])
at [Source: { "foo": "bar", "undefined": "property" }; line: 1, column: 31] (through reference chain: ch.hasselba.JacksonPlayground.Demo["undefined"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)
at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:817)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:954)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1315)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1293)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:249)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:136)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3560)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2576)
at ch.hasselba.JacksonPlayground.App.main(App.java:24)
null
But there are two annotations to the rescue, @JsonAnyGetter & @JsonAnySetter. By changing our Demo class and adding the following lines of code…
private Map<String, Object> others = new ConcurrentHashMap<String, Object>();
@JsonAnyGetter
public Map<String, Object> getOthers() {
return this.others;
}
@JsonAnySetter
public void addOther(final String name, final Object value) {
this.others.put(name, value);
}
… Jackson now puts all the unknown/undefined properties in the others map (uses the method defined by @JsonSetter). And then it uses the method with the @JsonGetter annotation when producing the JSON from the Demo instance.
{
"foo" : "bar",
"bar" : null,
"undefined" : "property"
}
What if we want to handle multiple „Demo“ objects in a JSON Array?
String content = "[ { \"foo\": \"bar\" }, {\"foo\": \"bar2\" } ]";
In this case we change our reading routine to work with lists:
// build the Object
List<Demo> test = null;
try {
test = mapper.readValue(content, mapper.getTypeFactory()
.constructCollectionType(List.class, Demo.class));
} catch (Exception e) {
e.printStackTrace();
}
In the result all entries are now contained in the list of Demo objects:
[ {
"foo" : "bar",
"bar" : null
}, {
"foo" : "bar2",
"bar" : null
} ]
Back to our RestApiApplication, have a look at this line:
objMapper.setSerializationInclusion(Include.NON_EMPTY);
This removes all empty properties globally from the generated output of our the servlet. So there is no need to add the @JsonIgnore annotation to any class. You can modifiy the globally used ObjectMapper in your servlet with multiple option, more will follow in another blog post.