Search code examples
javajsonjacksonmixins

Deserializing Parent Object based on id


I am trying to deserialize the "onclick" event contained within a "module" object as represented by the following JSON:

{
    "parent": {
        "id": 0,
        "children": [{
            "child": {
                "id": 1,
                "description": "A",
                "parent": 0,
                "events": [{
                    "onclick": {
                        "source": 1
                    }
                }]
            }
        }]
    }
}

The class structure is very simple:

View (Interface)
 +- AbstractView
    +- Parent
        +- Child

Event (Interface)
 +- AbstractEvent
    +- OnClick

AbstractEvent:

public abstract class AbstractEvent implements Event {

    private View source;

    protected AbstractEvent() {}

    protected AbstractEvent(View source) {
        this.source = source;
    }

    @Override
    public View getSource() {
        return source;
    }

    @Override
    public void setSource(View source) {
        this.source = source;
    }

}

I am using Mixins as the model is used between a few different projects.

ViewMixIn:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "type")
@JsonSubTypes({
    @Type(value = Parent.class, name = "parent"),
    @Type(value = Child.class, name = "child")
})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public abstract class ViewMixIn {

}

EventMixIn:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "class")
@JsonSubTypes({
    @Type(value = OnClick.class, name = "onclick")
})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "source")
public abstract class EventMixIn {

}

SimplifiedModule:

@Override
public void setupModule(SetupContext context) {
    super.setupModule(context);

    context.setMixInAnnotations(AbstractView.class, ViewMixIn.class);
    context.setMixInAnnotations(AbstractEvent.class, EventMixIn.class);

}

ObjectMapper configuration:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimplifiedModule())
    .setSerializationInclusion(NON_NULL)
    .setSerializationInclusion(NON_EMPTY)
    .enableDefaultTyping()
    .enableDefaultTyping(NON_FINAL, As.WRAPPER_OBJECT)
    .setVisibilityChecker(
        mapper.getSerializationConfig().getDefaultVisibilityChecker()
            .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
            .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
            .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
            .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));

As shown, the model is serialized correctly, but during deserialization I get the following error:

WARN 11:46:04:949 br.com.pdcore.appmaker.middleware.simplified.SimplifiedModelTest - Could not deserialise Parent. com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_NUMBER_INT), expected START_OBJECT: need JSON Object to contain As.WRAPPER_OBJECT type information for class br.com.pdcore.appmaker.middleware.simplified.model.view.View at [Source: {"parent":{"id":0,"children":{"java.util.ArrayList":[{"child":{"id":1,"description":"A","parent":0,"events":{"java.util.ArrayList":[{"onclick":{"source":1}}]}}},{"child":{"id":2,"description":"B","parent":0,"events":{"java.util.ArrayList":[{"onclick":{"source":2}}]}}},{"child":{"id":3,"description":"C","parent":0,"events":{"java.util.ArrayList":[{"onclick":{"source":3}}]}}},{"child":{"id":4,"description":"D","parent":0,"events":{"java.util.ArrayList":[{"onclick":{"source":4}}]}}},{"child":{"id":5,"description":"E","parent":0,"events":{"java.util.ArrayList":[{"onclick":{"source":5}}]}}}]}}}; line: 1, column: 145] (through reference chain: br.com.pdcore.appmaker.middleware.simplified.model.view.Parent["children"]->java.util.ArrayList[0]->br.com.pdcore.appmaker.middleware.simplified.model.view.Child["events"]->java.util.ArrayList[0]->br.com.pdcore.appmaker.middleware.simplified.model.event.OnClick["source"])

As though it is not understanding that the "source" id refers to the child object wrapping the onclick node.

If I change the MixIn definitions from:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "name")

to:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")

I still get a similar error:

WARN 12:04:35:126 br.com.pdcore.appmaker.middleware.simplified.SimplifiedModelTest - Could not deserialise Parent. com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_NUMBER_INT), expected FIELD_NAME: missing property '@class' that is to contain type id (for class br.com.pdcore.appmaker.middleware.simplified.model.view.View) at [Source: {"type":"br.com.pdcore.appmaker.middleware.simplified.model.view.Parent","id":0,"children":["java.util.ArrayList",[{"type":"br.com.pdcore.appmaker.middleware.simplified.model.view.Child","id":1,"description":"A","parent":0,"events":["java.util.ArrayList",[{"type":"br.com.pdcore.appmaker.middleware.simplified.model.event.OnClick","source":1}]]},{"type":"br.com.pdcore.appmaker.middleware.simplified.model.view.Child","id":2,"description":"B","parent":0,"events":["java.util.ArrayList",[{"type":"br.com.pdcore.appmaker.middleware.simplified.model.event.OnClick","source":2}]]},{"type":"br.com.pdcore.appmaker.middleware.simplified.model.view.Child","id":3,"description":"C","parent":0,"events":["java.util.ArrayList",[{"type":"br.com.pdcore.appmaker.middleware.simplified.model.event.OnClick","source":3}]]},{"type":"br.com.pdcore.appmaker.middleware.simplified.model.view.Child","id":4,"description":"D","parent":0,"events":["java.util.ArrayList",[{"type":"br.com.pdcore.appmaker.middleware.simplified.model.event.OnClick","source":4}]]},{"type":"br.com.pdcore.appmaker.middleware.simplified.model.view.Child","id":5,"description":"E","parent":0,"events":["java.util.ArrayList",[{"type":"br.com.pdcore.appmaker.middleware.simplified.model.event.OnClick","source":5}]]}]]}; line: 1, column: 331] (through reference chain: br.com.pdcore.appmaker.middleware.simplified.model.view.Parent["children"]->java.util.ArrayList[0]->br.com.pdcore.appmaker.middleware.simplified.model.view.Child["events"]->java.util.ArrayList[0]->br.com.pdcore.appmaker.middleware.simplified.model.event.OnClick["source"])


Solution

  • After reading JsonIdentityInfo's javadoc more closely the following identified the problem:

    Object id has to be serialized as a property in case of POJOs; object identity is currently NOT support for JSON Array types (Java arrays or Lists) or Java Map types.

    So basically I need to produce a custom deserializer.