What @JsonTypeInfo.ID to choose for property = "type.id" for deserialization, JsonTypeInfo.Id.CUSTOM? What @JsonTypeInfo.ID to choose for property = "type.id" for deserialization, JsonTypeInfo.Id.CUSTOM? json json

What @JsonTypeInfo.ID to choose for property = "type.id" for deserialization, JsonTypeInfo.Id.CUSTOM?


I know it's been 3 years since the original question, but dot-nested properties are still not supported and maybe this will help someone out. I ended up creating a class NestedTypeResolver so we can use the dot-syntax as expected. Simply add @JsonTypeResolver(NestedTypeResolver.class) to any class with nested discriminators and the poster's original attempt will work:

/**  * My ActivityDisplayModel Abstract Class */@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type.id")  @JsonSubTypes({  @JsonSubTypes.Type(value = MealDisplayModel.class, name = "MEAL"),  @JsonSubTypes.Type(value = EntertainmentDisplayModel.class, name = "ENTERTAINMENT")})@JsonTypeResolver(NestedTypeResolver.class)public abstract class ActivityDisplayModel {

NestedTypeResolver:

/** * Allows using nested "dot" dyntax for type discriminators. To use, annotate class with @JsonTypeResolver(NestedTypeResolver.class) */public class NestedTypeResolver extends StdTypeResolverBuilder {    @Override    public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType,            Collection<NamedType> subtypes) {            //Copied this code from parent class, StdTypeResolverBuilder with same method name            TypeIdResolver idRes = idResolver(config, baseType, subtypes, false, true);            return new NestedTypeDeserializer(baseType, idRes, _typeProperty, _typeIdVisible,                null, _includeAs);    }}

All the heavy work is done in here, NestedTypeDeserializer:

/** * Heavy work to support {@link NestedTypeResolver} */public class NestedTypeDeserializer extends AsPropertyTypeDeserializer {    private static final Logger LOGGER = LoggerFactory.getLogger(NestedTypeDeserializer.class);    public NestedTypeDeserializer(JavaType bt,            TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible,            JavaType defaultImpl) {        super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl);    }    public NestedTypeDeserializer(JavaType bt, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible,            JavaType defaultImpl, JsonTypeInfo.As inclusion) {        super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl, inclusion);    }    public NestedTypeDeserializer(AsPropertyTypeDeserializer src, BeanProperty property) {        super(src, property);    }    @Override    public TypeDeserializer forProperty(BeanProperty prop) {        return (prop == _property) ? this : new NestedTypeDeserializer(this, prop);    }    @Override    public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {        JsonNode originalNode = p.readValueAsTree();        JsonNode node = originalNode;        //_typePropertyName is the dot separated value of "property" in @JsonTypeInfo        LOGGER.debug("Searching for type discriminator [{}]...", _typePropertyName);        for (String property : _typePropertyName.split("\\.")) { //traverse down any nested properties            JsonNode nestedProp = node.get(property);            if (nestedProp == null) {                ctxt.reportWrongTokenException(p, JsonToken.FIELD_NAME,                        "missing property '" + _typePropertyName + "' that is to contain type id  (for class "                                + baseTypeName() + ")");                return null;            }            node = nestedProp;        }        LOGGER.debug("Found [{}] with value [{}]", _typePropertyName, node.asText());        JsonDeserializer<Object> deser = _findDeserializer(ctxt, "" + node.asText());        //Since JsonParser is a forward-only operation and finding the "type" discriminator advanced the pointer, we need to reset it        //Got clues from https://www.dilipkumarg.com/dynamic-polymorphic-type-handling-jackson/        JsonParser jsonParser = new TreeTraversingParser(originalNode, p.getCodec());        if (jsonParser.getCurrentToken() == null) {            jsonParser.nextToken();        }        return deser.deserialize(jsonParser, ctxt);    }}

Disclaimer: we've been using this for a month with Jackson 2.8.10 and have had no issues, but we had to go deep into the Jackson source code weeds to accomplish it, so YMMV. Hopefully Jackson will allow this out-of-the-box someday so we dont need these workarounds.


I am not sure that you can do it with specifying inner property: type.id. In my opinion you should change your JSON to simpler version. If you can not force your JSON supplier to change JSON schema you have to do it manually. Assume that your JSON looks like below:

{    "activityDisplayModel": {        "name": "lunch with friends",        "type": {            "id": "MEAL",            "description": "Meal"        },        "complete": false    }}

Below POJO classes fit to above JSON:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")@JsonSubTypes({    @JsonSubTypes.Type(value = MealDisplayModel.class, name = "MEAL"),    @JsonSubTypes.Type(value = EntertainmentDisplayModel.class, name = "ENTERTAINMENT")})abstract class ActivityDisplayModel {    protected String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return name;    }}class MealDisplayModel extends ActivityDisplayModel {    private boolean complete;    public boolean isComplete() {        return complete;    }    public void setComplete(boolean complete) {        this.complete = complete;    }    @Override    public String toString() {        return "MealDisplayModel [complete=" + complete + ", toString()=" + super.toString() + "]";    }}@JsonIgnoreProperties("complete")class EntertainmentDisplayModel extends ActivityDisplayModel {    @Override    public String toString() {        return "EntertainmentDisplayModel [toString()=" + super.toString() + "]";    }}class Root {    private ActivityDisplayModel activityDisplayModel;    public ActivityDisplayModel getActivityDisplayModel() {        return activityDisplayModel;    }    public void setActivityDisplayModel(ActivityDisplayModel activityDisplayModel) {        this.activityDisplayModel = activityDisplayModel;    }    @Override    public String toString() {        return activityDisplayModel.toString();    }}

Below script shows how you can parse above JSON:

ObjectMapper mapper = new ObjectMapper();// Updated JSON in memoryObjectNode rootNode = (ObjectNode)mapper.readTree(json);ObjectNode activityDisplayModelNode = (ObjectNode)rootNode.path("activityDisplayModel");JsonNode typeNode = activityDisplayModelNode.path("type");activityDisplayModelNode.set("type", typeNode.path("id"));System.out.println("Result: " + mapper.convertValue(rootNode, Root.class));

Above script prints:

Result: MealDisplayModel [complete=false, toString()=lunch with friends]

Also see:

  1. Jackson Tree Model Example.
  2. Convert Java Object to JsonNode in Jackson.