Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Jackson Serializers of Same Return Type

Tags:

java

json

jackson

I am using Jackson for JSON serialization and have written a couple custom String serializers, one for each getter method of a class. The methods each return the same type, Set<String>, but each uses a different serializer.

Unfortunately, Jackson is not using each serializer, one for each method, but is using one serializer for both. It appears to take whatever method comes first alphabetically and uses its serializer for both methods. What I expect is that the serializer annotated on the first method is used for the first method, and the serializer annotated on the second method is used on the second method. Debugging appears to indicate that Jackson has the serializers in a map keyed by return type of the method (which is the same for both).

An Example:

public class FooBar {

  private Set<String> foos = new HashSet<String>();
  private Set<String> bars = new HashSet<String>();

  @JsonProperty("FooWrapper")
  @JsonSerialize(contentUsing = FooSerializer.class)
  public Set<String> getFoos() {
    return foos;
  }

  @JsonProperty("BarWrapper")
  @JsonSerialize(contentUsing = BarSerializer.class)
  public Set<String> getBars() {
    return bars;
  }
}

Any suggestions on how to get the getFoos() method to serialize with a FooSerializer, and the getBars() method to serialize with the BarSerializer? In this example, the BarSerializer is invoked for both methods.

Note, if I change the signature of one of the methods to another collection type so they are different - List<String> for example - the serialization works.

Thanks in advance.

like image 851
SingleShot Avatar asked Nov 17 '13 23:11

SingleShot


1 Answers

I think what you are trying to achieve isn't possible in version 1.9.xx when using an ObjectMapper in combination with @JsonSerialize(contentUsing = BarSerializer.class).

Jackson does indeed cache the serializers, and it caches them based on the JavaType (in this case Set<String>) that's associated with the serializer. See StdSerializerProvider.findValueSerializer(JavaType valueType, BeanProperty property).

Although the BeanProperty is passed to this method, it isn't used as part of the cache key. You could subclass StdSerializerProvider and take the BeanProperty parameter into account when caching value serializers, but that's probably not the easiest way to solve your problem.

As a quick fix would be to use @JsonSerialize(using = FooCollectionSerializer.class) and deal with serializing the collection yourself. By doing this, the serializer is directly coupled to the BeanPropertyWriter used to serialize the property. When using @JsonSerialize(contentUsing = BarSerializer.class) there's no serializer coupled to the BeanPropertyWriter which triggers the serializer lookup that caches the serializers based on the JavaType

public class FooBar {

    private Set<String> foos = new HashSet<>();
    private Set<String> bars = new HashSet<>();

    @JsonProperty("FooWrapper")
    @JsonSerialize(using = FooCollectionSerializer.class)
    public Set<String> getFoos() {
        return foos;
    }

    @JsonProperty("BarWrapper")
    @JsonSerialize(using = BarCollectionSerializer.class)
    public Set<String> getBars() {
        return bars;
    }

    public static class FooCollectionSerializer extends JsonSerializer<Collection<String>> {

        JsonSerializer<Collection<String>> serializer;

        public FooCollectionSerializer() {
            //let Jackson deal with serializing the collection and just specify how you want to serialize indivial items
            this.serializer = new StringCollectionSerializer(null, new FooSerializer());
        }

        @Override
        public void serialize(Collection<String> value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            serializer.serialize(value, jgen, provider);
        }
    }

    public static class FooSerializer extends SerializerBase<String> {
        public FooSerializer() {
            super(String.class);
        }

        @Override
        public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeString(value);
        }
    }

    public static class BarCollectionSerializer extends JsonSerializer<Collection<String>> {

        @Override
        public void serialize(Collection<String> values, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            //handle serializing the collection yourself
            jgen.writeStartArray();
            for (String value : values) {
                jgen.writeString(value);

            }
            jgen.writeEndArray();
        }
    }
}
like image 138
Pieter Avatar answered Nov 17 '22 19:11

Pieter