Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON Jackson - exception when serializing a polymorphic class with custom serializer

Tags:

java

json

jackson

I'm currently migrating some code from Jackson 1.x to Jackson 2.5 json mapper and came a long a problem that wasn't there in 1.x.

This is the setup (see code below):

  • interface IPet
  • class Dog implements IPet
  • IPet is annotated with @JsonTypeInfo and @JsonSubTypes
  • class Human has a property of type IPet that is annotated with @JsonSerialize(using=CustomPetSerializer.class)

The problem: If I serialize an instance of Dog it works as expected (also the type info is added to the json string by Jackson). However when I serialize an instance of the Human class an exception is thrown saying:

com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type com.pet.Dog (through reference chain: com.Human["pet"])

The serialize(...) method of the CustomPetSerializer class is not invoked (tested using a breakpoint).

The code:

IPet implementation:

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
@JsonSubTypes({
     @JsonSubTypes.Type(value=Dog.class,    name="dog")
    //,@JsonSubTypes.Type(value=Cat.class,  name="cat")
    //more subtypes here...
})
public interface IPet
{
    public Long getId();
    public String getPetMakes();
}

Dog implementation:

public class Dog implements IPet
{
    @Override
    public String getPetMakes()
    {
        return "Wuff!";
    }

    @Override
    public Long getId()
    {
        return 777L;
    }
}

Human who owns a dog:

public class Human
{
    private IPet pet = new Dog();

    @JsonSerialize(using=CustomPetSerializer.class)
    public IPet getPet()
    {
        return pet;
    }
}

CustomPetSerializer implementation:

public class CustomPetSerializer extends JsonSerializer<IPet>
{
    @Override
    public void serialize(IPet value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException
    {
        if(value != null && value.getId() != null)
        {
            Map<String,Object> style = new HashMap<String,Object>();
            style.put("age", "7");
            gen.writeObject(style);
        }
    }
}

JUnit test method:

@Test
public void testPet() throws JsonProcessingException
{
    ObjectMapper mapper = new ObjectMapper();

    Human human = new Human();

    //works as expcected
    String json = mapper.writeValueAsString(human.getPet());
    Assert.assertNotNull(json);
    Assert.assertTrue(json.equals("{\"type\":\"dog\",\"id\":777,\"petMakes\":\"Wuff!\"}"));

    //throws exception: Type id handling not implemented for type com.pet.Dog (through reference chain: com.Human["pet"])
    json = mapper.writeValueAsString(human);    //exception is thrown here
    Assert.assertNotNull(json);
    Assert.assertTrue(json.contains("\"age\":\"7\""));
}
like image 741
Humppakäräjät Avatar asked Jan 10 '15 12:01

Humppakäräjät


2 Answers

You'll need to additionally override serializeWithType within you CustomPetSerializer because IPet is polymorphic. That's also the reason why serialize is not called. Check this related SO question that explains in detail when serializeWithType is called. For instance, your serializeWithType implementation might look something like this:

@Override
public void serializeWithType(IPet value, JsonGenerator gen, 
        SerializerProvider provider, TypeSerializer typeSer) 
        throws IOException, JsonProcessingException {

  typeSer.writeTypePrefixForObject(value, gen);
  serialize(value, gen, provider); // call your customized serialize method
  typeSer.writeTypeSuffixForObject(value, gen);
}

which will print {"pet":{"type":"dog":{"age":"7"}}} for your Human instance.

like image 91
edi Avatar answered Oct 23 '22 19:10

edi


Since Jackson 2.9 writeTypePrefixForObject() and writeTypeSuffixForObject() have been deprecated (I'm unclear why). It seems under the new approach it would now be:

@Override
public void serializeWithType(IPet value, JsonGenerator gen, 
        SerializerProvider provider, TypeSerializer typeSer) 
        throws IOException, JsonProcessingException {

  WritableTypeId typeId = typeSer.typeId(value, START_OBJECT);
  typeSer.writeTypePrefix(gen, typeId);
  serialize(value, gen, provider); // call your customized serialize method
  typeSer.writeTypeSuffix(gen, typeId);
}

So an extra line now, so not sure why it's a step forward, perhaps it's more efficient reusing the typeId object.

Source: Jackson's ObjectNode class currently in master. Not the best source but couldn't see any upgrade docs explaining what to do.

like image 22
dansoton Avatar answered Oct 23 '22 20:10

dansoton