Overriding default Serialization/Deserialization behaviour of JackSon json serializer

JSON is a desired media type for RESTFUL services. Jackson JSON library which is one of the preferred JSON provider for the Serialization/Deserialization of the JSON to Java Objects.

Recently I encountered a strange issue where in a UI library wasn’t producing desired JSON values for the boolean fields (true/false) but instead was producing values as (“y”/”n”), the strange behavior was also observed when it received (true/false) from the server as the desired values are (“y”/”n”).

The solution to this problem simply lied in customizing Serializing DeSerializing behavior of the Jackson Library.

Let’s start with the deserialization issue. Jackson provides API(s) to directly supply customized DeSerializers for the same.

We shall start with the ObjectMapper which provides functionality to convert between Java objects and matching JSON constructs.

Let’s subclass the ObjectMapper into


public class CustomObjectMapper extends ObjectMapper {

    public CustomObjectMapper() {

    }

}

In the constructor we can provide deserializing configuration(s) along with customized deserializers.

Prepare an object of the SimpleDeserializers. It’s a convenience java class supplied by Jackson library to add extra deserializers.

SimpleDeserializers simpleDeserializers = new SimpleDeserializers();

Now we would prepare a boolean deserializer for the same, our customized one.

private class BooleanDeserializer<T extends Object> extends
 JsonDeserializer<Boolean> {
 final protected Class<?> _valueClass = Boolean.class;

@Override
 public Boolean deserialize(JsonParser jp, DeserializationContext ctxt)
 throws IOException, JsonProcessingException {
 // TODO Auto-generated method stub
 return _parseBooleanPrimitive2(jp, ctxt);
 }

protected final boolean _parseBooleanPrimitive2(JsonParser jp,
 DeserializationContext ctxt) throws IOException,
 JsonProcessingException {
 JsonToken t = jp.getCurrentToken();
 if (t == JsonToken.VALUE_TRUE) {
 return true;
 }
 if (t == JsonToken.VALUE_FALSE) {
 return false;
 }
 if (t == JsonToken.VALUE_NULL) {
 return false;
 }
 if (t == JsonToken.VALUE_NUMBER_INT) {
 return (jp.getIntValue() != 0);
 }
 if (t == JsonToken.VALUE_STRING) {
 String text = jp.getText().trim();
 if ("true".equals(text)) {
 return true;
 }
 if ("false".equals(text) || text.length() == 0) {
 return Boolean.FALSE;
 }

if ("n".equals(text) || text.length() == 0) {
 return Boolean.FALSE;
 }

if ("y".equals(text)) {
 return Boolean.TRUE;
 }
throw ctxt.weirdStringException(_valueClass,
 "only \"true\" or \"false\" recognized");
 }
 // Otherwise, no can do:
 throw ctxt.mappingException(_valueClass);
 }
 }

This boolean deserializer is a generified class subclassing the JsonDeserializer, which is supplied to handle the deserialization of the values which are generally not supported by the default boolean  deserializer. Here we have overridden the

public Boolean deserialize(JsonParser jp, DeserializationContext ctxt) method and delegated the call to the

protected final boolean _parseBooleanPrimitive2(JsonParser jp,
 DeserializationContext ctxt) throws IOException,
 JsonProcessingException

In this method we obtain the token from the JsonParser and check for the usual values. This is done to make sure that the default behavior is not affected by the customized part. The implementation is identical to the Boolean Deserializer supplied by the StdDeserializer class of the JackSon library. Next, we fetch the values from the json parser and check whether they match with the values we desire. In case they do match we return the appropriate values else we raise an exception.

Now we would add our deserializer to the simpleDeserializers. To this use the following API.

simpleDeserializers.addDeserializer(Boolean.class,new BooleanDeserializer());

Here simply we have added the BooleanDeserizliaer for the deserialization of json values to type Boolean.

Next we need to do a few configuration steps.

Prepare an object of the StdDeserializerProvider

StdDeserializerProvider stdDeserializerProvider = new StdDeserializerProvider();
// Add additional Deserializers instance to it.
stdDeserializerProvider.withAdditionalDeserializers(simpleDeserializers);

//Set the Deserializer provider in the customized Object Mapper.
setDeserializerProvider(stdDeserializerProvider);

And you are done with it. Basically when you add additional deserializers they get priority over the default deserializers for the Class/Type already supplied by the jackson library.

Now , the tricky part.

Serialization is perhaps more trickier than the DeSerialization as, here we are trying to override the standard behaviour which is not supported directly and we need to imply a few things for the same.

We would start off with creating a customJson factory which could handle customization where required.

public class CustomJsonFactory extends MappingJsonFactory {
 public CustomJsonFactory(){
 super();
 }

}

Here CustomJson Factory extends from MappingJsonFactory so that most of the default implementation could be harnessed, overriding only the required portion. Now we need to sub class the JsonGenerator to control the serialization for the desired type. There are two implementations of the abstract class JsonGenerator supplied by Jackson. WriterBasedGenerator and Utf8Generator. WriterBasedGenerator is declared final and hence couldn’t be used. We shall subclass Utf8Generator and override the desired behavior.

public final static class CustomUTF8Generator extends Utf8Generator {

private final static byte[] ONE_BYTE = { 'y' };
 private final static byte[] ZERO_BYTE = { 'n' };

public CustomUTF8Generator(IOContext ctxt, int features,
 ObjectCodec codec, OutputStream out) {
 super(ctxt, features, codec, out);
 }

@Override
 public void writeBoolean(boolean state) throws IOException,
 JsonGenerationException {
 _verifyValueWrite("write boolean value");
 if ((_outputTail + 5) >= _outputEnd) {
 _flushBuffer();
 }
 byte[] keyword = state ? ONE_BYTE : ZERO_BYTE;
 int len = keyword.length;
 System.arraycopy(keyword, 0, _outputBuffer, _outputTail, len);
 _outputTail += len;

 }
 }

Here we simply override the

public void writeBoolean(boolean state) throws IOException, JsonGenerationException 

to provide the desired behavior.

In this method we simply copy the byte array value of the ONE_BYTE or ZERO_BYTE array (depending on the boolean value) to the keyword byte array and  increase the outputTail by the length of the keyword array. This simply outputs the desired character as the serialized json value. Now we set this customized JsonGenerator into the custom json factory. This is done by overriding these two methods in the CustomJsonFactory.

@Override
 protected JsonGenerator _createJsonGenerator(Writer out, IOContext ctxt)
 throws IOException {
 return new CustomUTF8Generator(
 ctxt, _generatorFeatures, _objectCodec,
 new WriterOutputStream(out));
 }

This case requires little tweaking and we require to convert the java.io.Writer object to an output stream. I am using a convenience utility

org.apache.commons.io.output.WriterOutputStream.WriterOutputStream(Writer writer) from the commons-io ( version 2.0.1) for the same.
@Override
 protected JsonGenerator _createUTF8JsonGenerator(OutputStream out,
 IOContext ctxt) throws IOException {
 // TODO Auto-generated method stub
 return new CustomUTF8Generator(
 ctxt, _generatorFeatures, _objectCodec,
 out);
 }

In this overriding case we simply add the CustomUTF8Generator.

Now with this stuff completed, our CustomJsonFactory is ready to be set in the objectMapper with this call.

public CustomObjectMapper() {
 /**
 * Serialization stuff
 */
 super(new CustomJsonFactory());
 }

add this as the first line of the constructor in the object mapper, and you are done. Register the developed ObjectMapper instance and you can see the desired serialization/deserialization exhibited by it.

About these ads

2 thoughts on “Overriding default Serialization/Deserialization behaviour of JackSon json serializer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s