DigitalJoel

2015/03/18

How to make Jackson serialize null strings differently than null Objects

Filed under: development — Tags: , , , , — digitaljoel @ 3:36 pm

We’ve got an API.  It uses a certain technology to write JSON data, and this certain technology writes null strings as empty string (“”), but writes null objects as ‘null’.  It’s in production, and therefore changes to it need to be backward compatible.  Jackson is awesome. We want to replace “certain technology” with Jackson.

My first thought was that I would write a custom StringSerializer and associate it with the String type and then whenever a String is null I would output empty string instead of null and poof, done.  Sadly, in Jackson, if the value is null it will always call the NullValueSerializer instead of the Serializer that is configured for the type of field that contains the null value.

So, how to overcome this conundrum?  It can still be approached with a custom serializer implementation, but you also need to touch the SerializerProvider.  Here’s a quick solution we threw together that appears to be working.

// We need to customize the DefaultSerializerProvider so that when it is looking for a NullSerializer it
// will use one that is class sensitive, writing strings as "" and everything else using the default value.
public static class CustomNullStringSerializerProvider extends DefaultSerializerProvider {

  // A couple of constructors and factory methods to keep the compiler happy
  public CustomNullStringSerializerProvider() { super(); }
  public CustomNullStringSerializerProvider(CustomNullStringSerializerProvider provider, SerializationConfig config,
    SerializerFactory jsf) {
    super(provider, config, jsf);
  }
  @Override
  public CustomNullStringSerializerProvider createInstance(SerializationConfig config,
    SerializerFactory jsf) {
    return new CustomNullStringSerializerProvider(this, config, jsf);
  }

  // This is the interesting part.  When the property has a null value it will call this method to get the
  // serializer for that null value.  At this point, we have the BeanProperty, which contains information about
  // the field that we are trying to serialize (including the type!)  So we can discriminate on the type to determine
  // which serializer is used to output the null value.
  @Override
  public JsonSerializer<Object> findNullValueSerializer(BeanProperty property) throws JsonMappingException {
    if (property.getType().getRawClass().equals(String.class)) {
      return EmptyStringSerializer.INSTANCE;
    } else {
      return super.findNullValueSerializer(property);
    }
  }
}

// This is our fancy serializer that takes care of writing the value desired in the case of a null string.  We could
// write whatever we want in here, but in order to maintain backward compatibility we choose the empty string
// instead of something like "joel is awesome."
public static class EmptyStringSerializer extends JsonSerializer<Object> {
  public static final JsonSerializer<Object> INSTANCE = new EmptyStringSerializer();

  private EmptyStringSerializer() {}

  // Since we know we only get to this seralizer in the case where the value is null and the type is String, we can
  // do our handling without any additional logic and write that empty string we are so desperately wanting.
  @Override
  public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
    throws IOException, JsonProcessingException {

    jsonGenerator.writeString("");
  }
}

Now, when we are configuring the ObjectMapper we can simply call setSerializerProvider and give it our custom provider and voila.

2012/10/08

Jackson Mixins

Filed under: development, java — Tags: , , — digitaljoel @ 8:21 pm

You are working with a third party library. You need to serialize an object from that library to JSON. Or, in my case, I needed to serialize an implementation of an interface defined in the third party library. In any case you can’t modify the class you need to serialize, but you also need to change the way the class is mapped. Jackson provides a great mechanism to work around this using what they call MixIns.

Let’s say you are given this:

public interface ThirdPartyInterface {
  long getItemID();
  float getValue();
}

For some reason beyond your control, you need to use id instead of itemID in the JSON as the key for getItemID. To make things even more awesome, you need to have quotes around the itemID and the value, but you don’t want quotes around EVERY numeric field, just those two. With Jackson, I create my mixin interface, and I provide the Jackson annotations that I would like to apply on the instances of the ThirdPartyInterface to my new one.

public interface MyMixin {
  @JsonProperty("id")
  @JsonSerialize(using=ToStringSerializer.class)
  long getItemID();

  @JsonProperty("value")
  @JsonSerialize(using=ToStringSerializer.class)
  float getValue();
}

Now you can see that I have the JsonProperty annotations in there to change the name of the key, and I have the JsonSerialize annotation in there, using the Jackson builtin ToStringSerializer to convert the long and float values into Strings, which will ensure they are quoted in the JSON output.

In order to use my Mixin, I configure the ObjectMapper to do so as follows.

  ObjectMapper mapper = new ObjectMapper();
  mapper.getSerializationConfig().addMixInAnnotations(ThirdPartyInterface.class, MyMixin.class);

In my case, I have several implementations of the ThirdPartyInterface and configuring the mixin as above applies to all implementations. Pretty slick.

Create a free website or blog at WordPress.com.