具有嵌套在对象中的类型属性的杰克逊多态反序列化

我正在尝试找到一种方法来使用 jackson 的多态反序列化功能,它会根据嵌套在标题/控件对象中的属性反序列化我的对象:

I'm trying to find out a way to use the polymorphic deserialization feature of jackson in a way that it will deserialize my object based on a property that is nested in header/control object:

JSON 1 - 类别 1:

JSON 1 - CATEGORY1:

{
 "id":"someId",
 "header":{
           "category":"CATEGORY1",
           "somOtherProperty":"someValue"
          }
 "nextField":"nextValue",
 ...
}

JSON 2 - 类别 2

JSON 2 - CATEGORY2

{
 "id":"someId",
 "header":{
           "category":"CATEGORY2",
           "somOtherProperty":"someValue"
          }
 "nextField":"nextValue",
 ...
}

父类(类似这样的注释)

Parent Class (annotations something like this)

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "category")
@JsonSubTypes({
        @Type(value = Category1Class.class, name = "CATEGORY1"), 
        @Type(value = Category2Class.class, name = "CATEGORY2") })
public class ParentClass{
    private Header header;
    private String nextField;
    ...
}

public class Header{
    private String category;
    private String somOtherProperty;
    ...
}

儿童班

@JsonTypeName("CATEGORY1")
public class Category1Class extends ParentClass{
    ...
}

@JsonTypeName("CATEGORY2")
public class Category2Class extends ParentClass{
    ...
}

jackson 中是否有开箱即用的功能可以让我进行这种反序列化,或者我错过了什么?

Is there an out of the box functionality in jackson that would enable me to do this kind of deserialization or am I missing something?

推荐答案

如果你看Jackson Api AsPropertyTypeDeserializer 是负责使用属性识别子类型的类.如果您查看该类,则有一个名为 deserializeTypedFromObject 的方法,它将使用 JsonTypeIdResolver 识别子类.我们可以扩展这个类并覆盖方法 deserializeTypedFromObjectforProperty.

If you look at the Jackson Api AsPropertyTypeDeserializer is the class responsible for sub type identification using property. If you look at that class there is a method called deserializeTypedFromObject which will identify the subclass using JsonTypeIdResolver. We can extend this class and override methods deserializeTypedFromObject and forProperty.

package com.dilipkumarg.tutorials.dynamicsubtype;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer;
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
import com.fasterxml.jackson.databind.type.SimpleType;

public class CustomTypeDeserializer extends AsPropertyTypeDeserializer {
public CustomTypeDeserializer(
        final JavaType bt, final TypeIdResolver idRes,
        final String typePropertyName, final boolean typeIdVisible, final Class<?> defaultImpl) {
    super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl);
}

public CustomTypeDeserializer(
        final AsPropertyTypeDeserializer src, final BeanProperty property) {
    super(src, property);
}

@Override
public TypeDeserializer forProperty(
        final BeanProperty prop) {
    return (prop == _property) ? this : new CustomTypeDeserializer(this, prop);
}

@Override
public Object deserializeTypedFromObject(
        final JsonParser jp, final DeserializationContext ctxt) throws IOException {
    JsonNode node = jp.readValueAsTree();
    Class<?> subType = findSubType(node);
    JavaType type = SimpleType.construct(subType);

    JsonParser jsonParser = new TreeTraversingParser(node, jp.getCodec());
    if (jsonParser.getCurrentToken() == null) {
        jsonParser.nextToken();
    }
    /* 16-Dec-2010, tatu: Since nominal type we get here has no (generic) type parameters,
    *   we actually now need to explicitly narrow from base type (which may have parameterization)
    *   using raw type.
    *
    *   One complication, though; can not change 'type class' (simple type to container); otherwise
    *   we may try to narrow a SimpleType (Object.class) into MapType (Map.class), losing actual
    *   type in process (getting SimpleType of Map.class which will not work as expected)
    */
    if (_baseType != null && _baseType.getClass() == type.getClass()) {
        type = _baseType.narrowBy(type.getRawClass());
    }
    JsonDeserializer<Object> deser = ctxt.findContextualValueDeserializer(type, _property);
    return deser.deserialize(jsonParser, ctxt);
}

protected Class<?> findSubType(JsonNode node) {
    Class<? extends ParentClass> subType = null;
    String cat = node.get("header").get("category").asText();
    if (cat.equals("CATEGORY1")) {
        subType = Category1Class.class;
    } else if (cat.equals("CATEGORY2")) {
        subType = Category2Class.class;
    }
    return subType;
}
}

在扩展类中,我们使用 idResolver 绕过子类型识别,而是使用 header 字段的 category 字段动态识别.
我们需要 TypeResolverBuilder 来创建新的 CustomTypeDeserializer 实例.

In extended class we bypassed sub type identification using idResolver instead we are identifying dynamically with category field of header field.
We need TypeResolverBuilder to create new CustomTypeDeserializer instance.

package com.dilipkumarg.tutorials.dynamicsubtype;

import java.util.Collection;

import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;


public class CustomTypeResolver extends StdTypeResolverBuilder {
    @Override
    public TypeDeserializer buildTypeDeserializer(final DeserializationConfig config, final JavaType baseType, final Collection<NamedType> subtypes) {
        return new CustomTypeDeserializer(baseType, null,
            _typeProperty, _typeIdVisible, _defaultImpl);
    }
}

现在我们有一个 CustomTypeResolver 用于子类型识别,但是当 Jackon 找到ParentClass"时,它如何知道查看这个类?
我们可以通过两种方式做到这一点:

Now we have a CustomTypeResolver for sub type identification, but how Jackon will know to look this class when it found 'ParentClass'?
We can do it by two ways:

  1. 使用自定义配置扩展 JackonAnnotationInterceptor 并在创建 ObjectMapper 时对其进行配置.

  1. Extending JackonAnnotationInterceptor with custom configuration and configure it while creating ObjectMapper.

使用 @JsonTypeResolver 注释.这是推荐的方法,因为我们不需要配置任何东西.

Using @JsonTypeResolver annotation. This is the recommended approach as we don’t need to configure any thing.

在包含类型解析器后,我们的新 ParentClass 类将是:

After including type resolver our new ParentClass class will be:

package com.dilipkumarg.tutorials.dynamicsubtype;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;

@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonTypeResolver(CustomTypeResolver.class)
public class ParentClass {
    private Header header;
    private String nextField;
    ...
}

请参阅此处

相关文章