Jackson: create and register a custom JSON serializer with StdSerializer and SimpleModule classes

During Java objects JSON serialization sometimes we may need to change the default behavior and provide a customized representation of a class object or a collection. In this new (article about Jackson library we see how to implement a custom serializer, starting once again with a concrete example.
Suppose we have a class “SWEngineer” representing developers, characterized by an ID, a name and a list of known programming languages that consists of a strings array. The goal is to serialize the list of languages known by the developer as a single string, with values separated by a ‘;’ instead of serializing it as a list of strings representing all the array elements, enclosed in square brackets.

In Jackson we can do that by performing the following three steps:

  • Create a custom serializer extending the StdSerializer class
  • Create an object of SimpleModule class, adding to it the custom serializer and specifying for which class it must be used
  • Register the module on the ObjectMapper instance

We can proceed by steps and begin to define our class, for which we will then provide the custom serializer:

import java.util.ArrayList;
import java.util.List;

class SWEngineer {

    private long id;
    private String name;
    private String[] languages;

    public SWEngineer(long id, String name, String[] languages) {
        this.id = id;
        this.name = name;
        this.languages = languages;
    }

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String[] getLanguages() {
        return this.languages;
    }

    public void setLanguages(String[] languages) {
        this.languages = languages;
    }
}

Now we define an example class where we create two instances of the class SWEngineer and insert it into an ArrayList that then we serialize:

public class CustomSerDeserExample {

    public static void main (String[] args) {

        List<SWEngineer> sweList = new ArrayList<>();
        SWEngineer swe1 = new SWEngineer(1, "Mark", new String[]{"Java", "Python"});
        SWEngineer swe2 = new SWEngineer(2, "John", new String[]{"Java", "C++", "Ruby"});
        sweList.add(swe1);
        sweList.add(swe2);

        ObjectMapper mapper = new ObjectMapper();

        String s = null;

        try {
            s = mapper.writeValueAsString(sweList);
        }
        catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        System.out.println(s);
    }
}

By running this first test program we get the following result, where we can see that the SWEngineer class objects are serialized with the default JSON rules. In this case then, the strings array containing the list of languages is displayed with items enclosed in square brackets.

[{"id":1,"name":"Mark","languages":["Java","Python"]},{"id":2,"name":"John","languages":["Java","C++","Ruby"]}]

Our goal is precisely to change this default behavior and build a custom JSON serializer in order to represent the “languages” field of the SWEngineer objects not as a traditional array, but as a single string with the values separated by ‘;’ (semicolon).
So, in the JSON string produced, the values:

"languages":["Java","Python"]
"languages":["Java","C++","Ruby"]

should became like this:

"languages":"Java;Python;"
"languages":"Java;C++;Ruby;"

Let’s see how to proceed.
First, we create our serializer, by defining a class that extends the class StdSerializer supplied by Jackson. This class provides an abstract method serialize(), that we’ll override, defined as follows:

abstract void serialize(T value, JsonGenerator jgen, SerializerProvider provider)

What interests us is “value” which is the object that we have to serialize and “jgen” which is an implementation of the JsonGenerator abstract class that actually creates the string containing the serialization of the instances of the class for we are writing the custom serializer for. JsonGenerator provides methods to write fields of different types in the result string, such as writeNumberField, writeStringField, etc ..

We can now create our serializer and redefine the method serialize():

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;


class CustomSerializer extends StdSerializer<SWEngineer> {

    public CustomSerializer(Class<SWEngineer> t) {
        super(t);
    }

    @Override
    public void serialize(SWEngineer swe, 
                          JsonGenerator jgen,
                          SerializerProvider sp) throws IOException, JsonGenerationException {
        
        StringBuilder lang = new StringBuilder();
        jgen.writeStartObject();      
        jgen.writeNumberField("id", swe.getId());
        jgen.writeStringField("name", swe.getName());      
        
        for (String s: swe.getLanguages()) {
            lang.append(s).append(";");
        }    
        jgen.writeStringField("languages", lang.toString());
        
        jgen.writeEndObject();
    }
}

As we can see our CustomSerializer extends StdSerializer and in the serialize() method override we use writeStartObject() and writeEndObject() methods to start and end the serialized string and writeNumberField() and writeStringField() methods for writing respectively the values of “id” and “name” fields. The serialization of the “languages” field of the class SWEngineer is exactly the point where we have to act in order to change the default behavior and to perform the values concatenation. We create therefore a StringBuilder and loop over the array values, concatenating them to each other separated by a ‘;’. Again using the writeStringField() method we then write the resulting string as “languages” field value.

Before we can test our serializer, we proceed with the other two operations: create a SimpleModule that links our SWEngineer class custom serializer and then register it with the ObjectMapper. For the first one we just instantiate a SimpleModule object, giving it a name (and possibly a version adding as second parameter an instance of Version class), and call on it the addSerializer() method, defined as follows:

public SimpleModule addSerializer(JsonSerializer<?> ser)

Then to record the module just created on the ObjectMapper instance it’s enough to invoke the registerModule() method passing it the module as parameter.
We can now modify our testing program as follows:

public class CustomSerDeserExample {

    public static void main (String[] args) {

        List<SWEngineer> sweList = new ArrayList<>();
        SWEngineer swe1 = new SWEngineer(1, "Mark", new String[]{"Java", "Python"});
        SWEngineer swe2 = new SWEngineer(2, "John", new String[]{"Java","C++","Ruby"});
        sweList.add(swe1);
        sweList.add(swe2);

        ObjectMapper mapper = new ObjectMapper();

	// Create the module
        SimpleModule mod = new SimpleModule("SWEngineer Module");	
		
	// Add the custom serializer to the module
        mod.addSerializer(new CustomSerializer(SWEngineer.class));	
		
        mapper.registerModule(mod);	// Register the module on the mapper

        String s = null;

        try {
            s = mapper.writeValueAsString(sweList);
        }
        catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        System.out.println(s);
    }
}

Rerunning the test we get the following result:

[{"id":1,"name":"Mark","languages":"Java;Python;"},{"id":2,"name":"John","languages":"Java;C++;Ruby;"}]

As we can see the result is exactly what we wanted, because the “languages” attribute is valued not as an array but as a single string containing the different array elements separated by ‘;’.
Recall that the previous output, obtained with the default JSON serialization, was as follows:

[{"id":1,"name":"Mark","languages":["Java","Python"]},{"id":2,"name":"John","languages":["Java","C++","Ruby"]}]

Don’t worry, in the next article we will see how to create the relevant custom deserializer that will take the value of the unique string with which the field “languages” was serialized in JSON and, splitting it on the separator ‘;’, it will create the different strings to fill the array that represents the “languages” field in the SWEngineer Java class.

The complete example with all the classes can be downloaded here:

3 thoughts on “Jackson: create and register a custom JSON serializer with StdSerializer and SimpleModule classes

  1. Pingback: Jackson: create a custom JSON deserializer with StdDeserializer and JsonToken classes | Dede Blog

  2. Pingback: Jackson: using @JsonSerialize (or @JsonDeserialize) annotation to register a custom serializer (or deserializer) | Dede Blog

  3. Hi Davis,

    Im hoping you can help me, Im trying to serialize a User (for example), but I need it to be apart of a larger response that includes other key/value pairs. My thoughts are, if I can pass in a map of key/values when serializing a user, my result can look something like this
    Map
    {
    data: user,
    key1: value1,
    key2: value2,
    key2: value3,

    }

    Do you know of a way to create a custom Jackson Serializer that can accept parameters?
    Thanks,
    Grant

Leave a Reply

Your email address will not be published. Required fields are marked *