Serialization and Deserialization with ObjectMapper
Table of Contents
This guide contains examples that show you how to serialize and deserialize from Java to JSON. It explains how to work with Strings, Objects, LocalDate, Optional, Lists, Maps, etc.
The code blocks are just test cases so you can verify the output and play with the examples. Note that, in real life, you shouldn’t create the
ObjectMapper each time you need to parse or generate JSON. It’s much more efficient to reuse it once you have configured it. This is, for instance, what you do in Spring with dependency injection.
Jackson – Serialize String
It’s a very simple case, yet confusing sometimes. A plain String does not get mapped magically to a JSON object (that is, between curly brackets and with a field name). When you send this via HTTP, Kafka or AMQP, just to mention some examples, it may happen that the consumer is expecting an object so it fails at deserialization. It’s a common mistake when developing Spring Controllers that return a String. In that case, you can create simple objects whose only intention is to wrap a String, or you can code your own serializers or deserializers.
Jackson – Serialize String to JSON Object
You can create your own Serializer for the String class, and configure it to create a basic JSON object that wraps the string value. In this case, you introduce the field
string. Note how simple it is:
- Use the default implementation
StdSerializerand override the
- Then, you use another default implementation, in this case for a module:
SimpleModule, and you just add the new serializer to it.
- You only need to register your module in the
ObjectMapperand all the String objects serialized by that mapper will be converted to JSON objects.
Remember that you should configure your
ObjectMapper objects in advance and then reuse them in your application. As you can imagine, you can have different instances, each one with its own configuration that you can use as you need.
As shown above, you can add custom behavior by registering modules in your
ObjectMapper. Most of the examples in this guide use this feature to provide custom configuration.
Jackson – Serialize a List of String
A list of String objects gets serialized by default as a simple JSON array. You could use a custom wrapper like the one from the previous example to get an array of JSON objects instead. As an alternative, you can also create your own class with a single field to act as a wrapper (see below
Jackson – Serialize a Map of String
Jackson treats Maps as JSON objects by default. It serializes a Map as an object whose keys are its fields, so it calls the key’s
toString() method (in this case it’s just the String). The Map values are serialized using the defaults unless you override them. You can also set Maps as values if you want nested JSON objects, or you can use Java objects that are serialized with the per-field strategy as usual.
Jackson – Serialize a String Wrapper class
This is the first example serializing a Java object. The default serializer takes all the public fields and all the fields that are accessible via getters. You can alter this behavior with annotations or custom serializers. In this example,
PersonName is used as a wrapper for a simple String. A list of Java objects gets serialized to a list of JSON objects containing the fields and their values. Note that there is no indication in JSON of the class name, only its fields.
Jackson – Serialize objects with LocalDate (default)
This example uses a more realistic Java Class, in this case using a LocalDate field (introduced in Java 8). To be able to serialize those objects in a meaningful way, you need to register the
JavaTimeModule, contained in the dependency library
jackson-datatype-jsr310. It serializes the fields as an array by default. You could also avoid including that extra dependency if you use your custom serializers and deserializers, but that doesn’t make much sense when there are extensions available.
Jackson – Serialize objects with LocalDate in ISO format
Configuring the format of the parsed LocalDate is possible in multiple ways. In this case, you can use your own module instead of the
JavaTimeModule, and add the
LocalDateSerializer manually. This way you can set the format to, for instance, an ISO standard. The result is a date in YYYY-MM-DD format.
Jackson – Deserialize List of Strings
You can parse a JSON array of Strings to a list in a very straightforward manner. Note that in the example the list is not typed, see the example below if you want to use explicit types.
Jackson – Deserialize and Unwrap Strings
One of the serializing examples showed how to wrap String values into JSON objects on-the-fly. This is the implementation of the deserializer for that specific case: you can implement your own deserializer and use the parser to read the JSON tree and extract values. This is just a simple example, but it shows the basics in case you want to build a more complex deserializer.
Note that this example also shows how to instruct ObjectMapper to deserialize to a typed List. You just need to create a
CollectionType object and pass it when parsing the JSON contents.
Jackson – Deserialize values using JsonNode
This code does the same as the previous one, but it uses the ObjectMapper’s
readTree() method to get a
JsonNode object and then use its methods to parse the values. In this case,
findValuesAsText() is all you need to extract all the values matching that field name to a list.
There are some other useful methods in that class to traverse the JSON up and down, retrieve values using a path, etc. It’s a convenient way of reading JSON contents without needing to create all the associated POJOs.
Jackson – Deserialize to simple Java object
If you try to use the Person class included before to deserialize the corresponding JSON contents, you’ll get an exception. This is due to not having an empty constructor that Jackson can use during its deserialization logic (using reflection utils). There are two common ways of making a plain Java object candidate for JSON deserialization: including an empty constructor or using Jackson annotations. The next two examples cover those scenarios.
Jackson – Deserialize to simple Java object with Empty Constructor
You can create a separate class as per the example or you can modify the existing one. Now you instruct the ObjectMapper to deserialize using the class with the empty constructor and everything should work as expected.
Note that you can use the
JavaTimeModule also when deserializing. In this case the example is using the default array format for LocalDate.
Jackson – Deserialize to simple Java object using Annotations
A popular way of using Jackson is by leveraging the annotations included in the
jackson-annotations library. As you can see, we can annotate the constructor that should be used to create the object (
@JsonCreator) and we just annotate the parameters to the fields we want to parse with
Jackson – Deserialize to a different Java class
This sample code is trying to deserialize a JSON object with an extra field that does not exist in
PersonEC, hobbies. This field actually belongs to a serialized
PersonV2 object. As the example proves, you can’t get backward compatibility by default in this case since the unknown property causes an error. But this has a very easy fix, as you’ll see next.
Jackson – Deserialize to Java object ignoring unknown fields with Configuration
You can configure the ObjectMapper to ignore unknown properties as in this example, setting the value of
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES to false. As a result, the property
hobbies is ignored since there is no field with such name in the class
PersonEC. With this configuration, all the unknown properties for all objects deserialized with this mapper will be ignored.
Jackson – Deserialize to Java object ignoring unknown fields with Annotation
You can also achieve the same result as in the previous example by annotating the classes that are the target of the deserialization with
@JsonIgnoreProperties. This way you have the ability to choose for which classes you want to be flexible (or strict, in case you change also the default configuration).
Ignoring JSON fields can be risky but it’s also a way to implement backward compatibility when classes are only including new fields for new versions. It can also help to implement Domain Driven Design when you’re making shallow copies of your data in different system modules, so you can have classes with only the fields you’re interested in, and safely ignore others.
Jackson – Deserialize to a Map
Sometimes, it might be convenient to deserialize Java objects as simple types. In these cases, you can instruct Jackson to deserialize the object to a Map. It will contain field names as keys and their deserialized contents as values.
Note that the default deserializers apply for basic field types in the Map. Jackson deserializes the date as a collection, same as the array of hobbies.
Jackson – Deserialize to a List of Maps
If you want to apply the previous approach to a collection of objects, you can use the already-covered
CollectionType to specify the type of collection you want to get as a result. But in this case, you don’t need it. The default behavior of the ObjectMapper when deserializing Objects in a List is to create a list of field-value maps.
Jackson – Deserialize to a List of Objects
In this case, you want to explicitly set the type of the collection to
PersonV2, otherwise you will get a Map as shown before. Remember that this strategy for passing types is needed because of Type Erasure, which prevents you from using generics as in
List<PersonV2> value = mapper.readValue(json, List<PersonV2>.class);.
Note that the object without the
hobbies field gets deserialized properly, and its value is set to
null. You don’t need to ignore the property in this case since the field is not present in JSON.
Jackson – Deserialize Optional
A more convenient way of dealing with not present fields is to make them
Optional in your POJOs. That allows you to use its powerful API to set default values, execute conditional code, etc. This is the same example as before just with a modified version of
PersonV2 that contains
hobbies as an
Jackson can be smart to transform not present values to empty Optionals instead of nulls. However, for this to work properly, you need to include the dependency
jackson-datatype-jdk8 and configure the ObjectMapper registering the