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

So my JSON looks like this:

{ "ActivityDisplayModel" : { "name" : "lunch with friends", "startTime" : "12:00:00", "type" : { "id" : "MEAL", "description" : "Meal" }, "complete" : false } } 

and I'm trying to find a way to get @JsonTypeInfo so as not to get @JsonTypeInfo at me for having a type parameter inside a type object. It works for me earlier when the type field was a string, and not the object itself, but for further processing I need this as an object. I know that the following does not work, and I assume that this is a way to use JsonTypeInfo.Id.CUSTOM , but after looking around the world, JSON came up with examples. Also, if it is possible using the objectMapper parameter, I have all ears.

 /** * 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") }) public abstract class ActivityDisplayModel { ... 

The above is what I want to do, but of course I get an exception:

 Could not read JSON: Could not resolve type id '{' into a subtype of [simple type, class ... .ActivityDisplayModel] 

For such a simple problem, just one level deeper into JSON, who would think that it would be so much trouble?

+6
source share
2 answers

I'm not sure if you can do this with an internal property: type.id In my opinion, you should change your JSON to a simpler version. If you cannot force the JSON provider to change the JSON schema, you must do it manually. Suppose your JSON looks like this:

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

Below the POJO classes correspond to the 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(); } } 

The following script shows how you can parse the above JSON :

 ObjectMapper mapper = new ObjectMapper(); // Updated JSON in memory ObjectNode 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 the script prints:

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

See also:

+2
source

I know that 3 years have passed since the initial question, but the properties of the nested points are still not supported, and perhaps this will help someone. I ended up creating the NestedTypeResolver class, so we can use the dot syntax as expected. Just add @JsonTypeResolver(NestedTypeResolver.class) to any class with nested discriminators, and the original poster 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 hard work is done 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 used this for a month with Jackson 2.8.10 and did not have any problems, but we needed to delve into the weeds of the Jackson source code to execute it, therefore YMMV. Hopefully Jackson will ever resolve this because of the box, so we don’t need these workarounds.

+1
source

Source: https://habr.com/ru/post/974845/


All Articles