Domino & REST: More about Jackson

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.

Dieser Beitrag wurde unter Apache Wink, Jackson, JEE, REST abgelegt und mit , , , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.