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

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):

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\""));
}

Upvotes: 23

Views: 21248

Answers (2)

dansoton
dansoton

Reputation: 411

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.

Upvotes: 23

edi
edi

Reputation: 3122

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.

Upvotes: 31

Related Questions