Jackson: create a custom JSON deserializer with StdDeserializer and JsonToken classes

In the previous post we saw how to create a custom JSON serializer with Jackson, in order to handle in a desired way the objects of a given class and get a representation of them different from the default one. This new article is its natural continuation so we’ll analyze the opposite process, creating a custom JSON deserializer that allows us to create an object of our class, starting from a string that represents its serialization in the format defined by the ad-hoc custom serializer. As for the creation of a custom serializer Jackson supply us for extension the class StdSerializer, similarly to define a relavant custom deserializer we can start with the extension of the StdDeserializer class, implementing the deserialize() method it inherits from the JsonDeserializer abstract class. This method is defined as follows:

public abstract T deserialize(JsonParser jp, DeserializationContext ctxt)
                       throws IOException, JsonProcessingException

so we can see that it returns an instance of the class T for which it’s defined as deserializer. JsonParser is the class that allows us to analyze the JSON and determine the field names, their type and extract their values. It provides us the nextValue() method that returns the type of the next element in the form of JsonToken enumerator value, such as “VALUE_NUMBER_INT”, “VALUE_NUMBER_FLOAT”, “VALUE_STRING”, etc. Once we have identified the token type, we can check his name for distinguish for example if it is the String type “name” element or the “languages” element and properly handle each of them. In the first case we simply have to read the string that represents the value of the field, while in the case of the “languages” element when we read the string we must split it according to the separator ‘;’ and create the strings array containing the known languages to be used to populate the “languages” instance member of our SWEngineer class.
To retrieve the name of the current element from JsonParser we use the getCurrentName() method, while to extract then the value of the id, which is of type long in the Java class, we use the getLongValue() method. To extract instead String type values we will use the getText() method.
In the light of the above our custom deserializer for SWEngineer class will be as follows:

class CustomDeserializer extends StdDeserializer<SWEngineer>{

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

    @Override
    public SWEngineer deserialize(JsonParser jp, DeserializationContext dc)
                                                throws IOException, JsonProcessingException {

        long id = 0;
        String name = null;
        String []languages = null;
        JsonToken currentToken = null;
        while ((currentToken = jp.nextValue()) != null) {
            switch (currentToken) {
                case VALUE_NUMBER_INT:
                    if (jp.getCurrentName().equals("id")) {
                        id = jp.getLongValue();
                    }
                    break;
                case VALUE_STRING:
                    switch (jp.getCurrentName()) {
                        case "name":
                            name = jp.getText();
                            break;
                        case "languages":
                            languages = jp.getText().split(";");
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
        }
        return new SWEngineer(id, name, languages);
    }
}

Once defined the SWEngineer class custom deserializer, as we did for the serializer, we need to add it to a SimpleModule object and than register the latter with the Jackson ObjectMapper instance we use to serialize and deserialize. If to add the custom serializer to a SimpleModule we used the addSerializer() method, now we will similarly use the addDeserializer() method in the following way:

mod.addDeserializer(SWEngineer.class, new CustomDeserializer(SWEngineer.class));

Later, when we will create the test class, we will add both custom serializer and deserializer to a module and we will register them on a mapper.
Before we proceed with the definition of a test class to verify if our deserializer works properly and, in general, if the whole process of custom serialization/deserialization of a SWEngineer class object is right, we redefine the toString() method of this class, in order to obtain an explanatory representation of the object useful to check its members value before and after the execution.
The SWEngineer class with toString() redefinition becomes as follows:

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;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("ID: ").append(this.id).append("\nName: ")
                .append(this.name).append("\nLanguages:");

        for (String s: this.languages) {
            sb.append(" ").append(s);
        }
        return sb.toString();
    }
}

Let’s recover, again from the previous post, the custom serializer we created for the SWEngineer class:

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();
    }
}

At this point we have everything we need to write our sample class to test the whole JSON serialization and deserialization process. So we create a class where we instantiate a SWEngineer object, we serialize it in a JSON string with the custom serializer and then we deserialize the JSON with our new deserializer creating a new object of our class.

public class CustomSerDeserExample {

    public static void main (String[] args) {

        SWEngineer swe1 = new SWEngineer(1, "Mark", new String[]{"Java", "Python", "C++", "Scala"});

        ObjectMapper mapper = new ObjectMapper();

        SimpleModule mod = new SimpleModule("MyModule");
        mod.addSerializer(new CustomSerializer(SWEngineer.class));
        mod.addDeserializer(SWEngineer.class, new CustomDeserializer(SWEngineer.class));
        mapper.registerModule(mod);

        System.out.println("--- ORIGINAL OBJECT ---\n" + swe1);

        String s = null;

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

        System.out.println("\n--- JAVA to JSON (Custom) ---\n" + s);

        SWEngineer sweOut = null;
        try {
            sweOut = mapper.readValue(s, SWEngineer.class);

        }
        catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("\n--- JSON to JAVA ---\n" + sweOut);
    }
}

The result of the execution of the test program is as follows:

--- ORIGINAL OBJECT ---
ID: 1
Name: Mark
Languages: Java Python C++ Scala

--- JAVA to JSON (Custom) ---
{"id":1,"name":"Mark","languages":"Java;Python;C++;Scala;"}

--- JSON to JAVA ---
ID: 1
Name: Mark
Languages: Java Python C++ Scala

As we can see the representation of the original object is printed according to the redefined toString(), then the program prints the JSON string produced by our serializer, where we can find the list of elements represented as a single string with values separated by ‘;’ and finally the last part of the output is the representation of the SWEngineer object recreated through custom deserializer that properly coincides with that of the original object.

The whole code of this example can be downloaded here:

2 thoughts on “Jackson: create a custom JSON deserializer with StdDeserializer and JsonToken classes

  1. Pingback: Jackson: create and register a custom JSON serializer with StdSerializer and SimpleModule classes | Dede Blog

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

Leave a Reply

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