Taco Steemers

A personal blog.

Notes on manually using Jackson (java)

Jackson can be used to do JSON and XML serialization and deserialization in Java. These notes are on how to use Jackson manually.

Jackson can be used to do JSON and XML serialization and deserialization in Java. These notes are on how to use Jackson manually. Baeldung has a good page as well .

The object mapper

When we want to manually (de)serialize we will use the ObjectMapper class. Note that ObjectMapper objects are not thread-safe! Creating new ObjectMapper instances is said to be expensive. A solution then is to make the ObjectMapper class a static final field on a singleton instance. Processing code would usually be created as a singleton instance anyway, such as a JAX-RS @Resource annotated class, Jakarta EE @ApplicationScoped annotated class or a Spring @Component annotated class.

Serializing a single object

Example example = new Example();
ObjectMapper objectMapper = new ObjectMapper();
String output = objectMapper.writer().writeValueAsString(example);

The output string will contain the object contents in the JSON format, There is also a writeValue method that can be used with other types such as File objects.

Deserializing a single object

ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(exampleString);
return mapper.convertValue(rootNode, Example.class);

The exampleString object should contain valid JSON.

Deserializing an embedded single object

ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(exampleString);
JsonNode exampleNode = rootNode.get("example");
return mapper.convertValue(exampleNode, Example.class);

Deserializing a list of objects of the same type

ObjectMapper objectMapper = new ObjectMapper();
List<Example> exampleList = mapper.readValue(requestBody, new TypeReference<List<Example>>() {});

The readValue method can also be used with other types such as File objects.

Checking whether a node is null

A node will be null if it was never set. It will be a NullNode if it was removed.

    ObjectMapper objectMapper = new ObjectMapper();
    JsonNode rootNode = mapper.readTree(exampleString);
    JsonNode exampleNode = rootNode.get("example");
    if (null == exampleNode || exampleNode instanceof NullNode) {
        // The node should be considered null.
    }

Using files and URLs directly

Example example = new Example();
objectMapper.writeValue(new File("target/example.json"), Example.class);
example = objectMapper.readValue(new URL("target/example.json"), Example.class);

Configure how to handle non-informative values during serialization

We can use JsonInclude annotations on classes to indicate what we want to do with unusual values. For example, we can indicate that we don't want keys with null values in the output.

@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class Example {
    ...
}

Missing values during deserialization will be set to null.

Configure how to handle unknown fields

We can add @JsonIgnoreProperties if we don't want deserialization to fail when we encounter values that are not in our class definition.

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class Example {
    ...
}

This is handy if an API we consume is not versioned on the URL or generated class, but versioned based on contents instead. In that situation we can, with proper planning by client and server, deserialize an object generated with any version of the API and process it correctly. Note that this will not work if the API changes the type of any field between versions. For example, if the "status" field switches from an integer to a String.

Handling invalid values that can't be excluded by JsonInclude annotations

Values that are not valid for certain types lead to runtime exceptions. The additional configuration in the example shown below returns null instead of throwing a runtime exception, when we try to parse a date time string from a different system that is not entirely compatible with ours. The server can send "0000-00-00T00:00:00Z" when it does not know the date/time. To allow our client to deserialize that invalid value we can add a handler like the following:

    objectMapper.addHandler(new ExampleDeserializationHandler() {

        @Override
        public Object handleWeirdStringValue(
                DeserializationContext ctxt, Class<?> targetType, String valueToConvert, String failureMsg) {
            if (targetType.equals(OffsetDateTime.class) && "0000-00-00T00:00:00Z".equals(valueToConvert)) {
                return null;
            }
            return valueToConvert;
        }
    });