Jackson is a framework that provides the means to read and write objects to and from JSON. Typically, it tries to use built in support for common classes and simple types. It will also use a default serializer based on reflection to write the JSON, but will need some guidance for more complex custom objects. You can do this using annotations, or alternatively, you can write custom serializers/deserializers. Here I’m going to look at Jackson serializers to save objects to a JSON file.

Most people’s first introduction to Jackson is using the default serializer through reflection and then using annotations or mixins. Annotations can be great but they can clutter code, cause additional dependencies and the more complex your situation, the less likely annotations are going to be enough to meet your needs. This is especially the case when you have to adhere to another JSON schema and don’t want to mirror that schema in your model with annotations.

Serializers are classes which can be used to serialize your objects into JSON. The advantage of these is that you have absolute control over the JSON that is produced. It does this by literally adding each element to the JSON document you are writing.

Benefits of Serializers

If you rely on annotations or even the default reflection serializers, you run the risk of having new fields introduced into your json documents and causing errors elsewhere, especially if it is being consumed by something that is strict about the expected content. Serializers make adding fields a very deliberate task around which you can put tests to ensure you are getting the JSON you expect.

For a little more work, you get complete control over your content and don’t have to worry about the limitations of annotations which sooner or later, in any larger project will pay off. By incorporating good design practices you can also construct a rich set of serializers that makes extending them easy.

The source code for the sample project is available on the blog post code repository Let’s look at a simple model for our serializer:

Let’s look at a simple model for our serializer:

class Person {
  private String firstName;
  private String lastName;
  private List<Pet> pets;
}

class Pet {
  private String name;
}

class Dog extends Pet {
  public enum Size {SMALL, MEDIUM, LARGE};
  private Size size;
}

class Snake extends Pet {
  private int length;
}
//getters/setters and convenience methods ommitted.

Each person has a list of pets which can be of different types. Polymorphism is often a problem when writing JSON because the JSON itself doesn’t indicate the type of the object being written. You can ask Jackson to persist a field to indicate the type so it can be deserialized later on. However, if you are dealing with a custom schema, that may not be an option, or the type may be based on some other logic.

We extend StdSerializer and implement the single method serialize. We also create an empty constructor which calls the super constructor with the class of object we are serializing.

We’ll start with our Person first and we’ll start by writing out our fields.

PersonSerializer.java:

public class PersonSerializer extends StdSerializer<Person> {

  protected PersonSerializer() {
    super(Person.class);
  }

  @Override
  public void serialize(final Person value, final JsonGenerator gen, final SerializerProvider provider)
      throws IOException {
    gen.writeStartObject();
    //write string field and field name in one method
    gen.writeStringField("firstName", value.getFirstName());
    gen.writeStringField("lastName", value.getLastName());
    //write the field name and then the value
    gen.writeFieldName("pets");
    gen.writeObject(value.getPets());
    gen.writeEndObject();
  }
}

When persisting the object, we use methods on the JsonGenerator to create content. We start by indicating we want to start a new object, and then we write our fields manually. When we call writeObject to write our pets, we pass in the object and Jackson will figure out how to serialize it. Finally, we call the method end our pet object .

Jackson will try and persist the Pet as a simple object using reflection to pull the properties and write them. This is alright if you don’t care about the final schema and you aren’t worrying about reading the JSON back in. Ideally, we want another serializer to write the Pet class taking into account all the different Pet subclass types.

Now lets look at serializing our Pet. We could write one serializer per concrete type, but we have the option of writing just a common PetSerializer class and all subclasses will use that serializer

For the pet, we want to write a value to indicate the type of Pet we are writing. We will make it the same as the simple class name for now. We then write the Pet fields and then fields that are specific to subclasses. One great thing about serializers is that you can utilise inheritance to make reusable serializers, or use composition to invoke another type serializer as needed.

public class PetSerializer extends StdSerializer<Pet> {

  public PetSerializer() {
    super(Pet.class);
  }

  @Override
  public void serialize(final Pet value, final JsonGenerator gen, final SerializerProvider provider)
      throws IOException {
    gen.writeStartObject();

    gen.writeStringField("type", value.getClass().getSimpleName());
    gen.writeStringField("name", value.getName());

    if (value instanceof Snake) {
      gen.writeNumberField("length", ((Snake) value).getLength());
    }
    if (value instanceof Dog) {
      gen.writeStringField("size", ((Dog) value).getSize().toString());
    }
    gen.writeEndObject();
  }
}

Putting It Together

To test this, we need to construct a Jackson ObjectMapper instance and add these serializers to it using a module. We can then use the object mapper to save any combination of Person and Pet instances and collections. Once registered, Jackson will use the serializer anytime it encounters an object of that type.

  public static void main(final String[] args) throws JsonProcessingException {
  public static void main(final String[] args) throws JsonProcessingException {
    //you can create this once and re-use it everywhere
    final ObjectMapper objectMapper = new ObjectMapper();
    final SimpleModule module = new SimpleModule("mySerializers");
    module.addSerializer(new PersonSerializer());
    module.addSerializer(new PetSerializer());
    objectMapper.registerModule(module);

    // build our models
    final Person p1 = ...
    final Person p2 = ...

    final Collection<Person> people = new ArrayList<>();
    people.add(p1);
    people.add(p2);

    // now write the json
    System.out.println("people unformatted is:\n" + objectMapper.writeValueAsString(people));

    // grab the pretty printer and store it
    final ObjectWriter prettyWriter = objectMapper.writerWithDefaultPrettyPrinter();

    // now write formatted json
    System.out.println("people are :\n" + prettyWriter.writeValueAsString(people));
    System.out.println("p1 is :\n" + prettyWriter.writeValueAsString(p1));
    System.out.println("p2 is :\n" + prettyWriter.writeValueAsString(p2));
    System.out.println("p2.pet(0) is :\n" + prettyWriter.writeValueAsString(p2.getPets().get(0)));
  }
}

Here we have Jackson write a collection of people, and individual Person, or an individual Pet and it will use the serializer. We also switch between using the pretty printer for displayable json, and the default writer which removes formatting and condenses the content.

Additional Thoughts

We technically, could/should put try/except blocks around the object/array start/end blocks like so :

gen.writeStartObject();
try {
  ...write properties to JSON...
} finally {
  gen.writeEndObject();
}

This is so we will always put the enclosing brace ‘}’ on our JSON object in the event that an exception is thrown while generating the content. Problem is that as soon as the exception is thrown, then you won’t write any more of the object to the document, and you could end up with a partial document and no exception thrown. For that reason, I think its better to throw the exception and terminate generating the document. However, your mileage may vary.

Serialize the object without context

When creating a serializer, you have to implement it is as a way of serializing the object only. Assume it knows nothing about the context it is being written in, whether its a ‘pet’ property, an array or a single item. For example, our PetSerializer writes the value for a single pet wrapped in a json object. Don’t assume it is being written as a pet property of another class and write the property name. Leave it to the invoking class to handle any content around the object being written. The PersonSerializer writes the “pets” field name then passes in the collection to be written. Jackson takes responsibility for writing the array start/end brackets of the collection and defers to the PetSerializer to write the Pet content. By doing this, you create serializers that are consistent and reusable. Don’t write serializers for collections of objects, just the object itself.