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

前端 未结 2 1961
梦毁少年i
梦毁少年i 2021-02-13 11:33

So I have JSON that looks like this:

{
    \"ActivityDisplayModel\" : {
        \"name\" : \"lunch with friends\",
        \"startTime\" : \"12:00:00\",
                 


        
2条回答
  •  [愿得一人]
    2021-02-13 11:51

    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 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 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.

    提交回复
    热议问题