具有动态类型的Jackson多态反序列化

时间:2017-01-26 15:40:55

标签: java json jackson

我有一个数据结构,包含一些强类型字段和一些松散类型的字段。其中一些字段是可以是任何深层嵌套的集合。

示例JSON

{
  "prop": "Hello",              //strongly-typed
  "child1": {
    "anInt": -1
  },
  "map": {                      // here magic begins
    "JustString": "JustValue",  // we may store any Object in this map
    "Item_With_Type": {
      "@type": "MyMap",         // some of them tell their type and we need to rely on it
      "Custom": "Value"
    },
    "List_With_All_Child1": {
      "@type": "MyMap[]",       // lists define the type of all values in it in this way
      "@values": [
        {
          "Key": "Value",       // MyMap is a Map
          "Child1": {           // of <? extends Object>
             "anInt": 2
           }
        },
        {
          "Key": "Value"
        }
      ]
    }
  }
}

我想在

上映射
public static class Parent {
    private String prop;
    private Child1 child1;
    private MyMap<?> map;
}

public static class Child1 {
    private int anInt;
}

public static class MyMap<T> extends HashMap<String, T> implements Map<String, T> {
}

(省略了访问者)

基本上我需要一种数据绑定器,杰克逊每次尝试解决任何字段保存环境的类型时都会询问类型,如果这个数据绑定器没有找到任何特定应用程序,杰克逊应该回退默认类型解析。

任何想法如何实现这一目标?

1 个答案:

答案 0 :(得分:0)

在与杰克逊玩了一段时间后,我找到了以下解决方案。对我来说很好。

首先,我们将所有内容都变为多态

@JsonTypeResolver(MyTypeResolver.class)
@JsonTypeIdResolver(MyTypeIdResolver.class)
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.PROPERTY, property = "@type")
public interface ObjectMixin {

}

ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.addMixIn(Object.class, ObjectMixin.class);

我们创建自定义TypeResolver,仅处理java.lang.Object的类型序列化/去除化。

public class MyTypeResolver extends StdTypeResolverBuilder {

    @Override
    public TypeSerializer buildTypeSerializer(SerializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
        return useForType(baseType) ? super.buildTypeSerializer(config, baseType, subtypes) : null;
    }

    @Override
    public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
        return useForType(baseType) ? super.buildTypeDeserializer(config, baseType, subtypes) : null;
    }

    public boolean useForType(JavaType t) {
        return t.isJavaLangObject();
    }
}

TypeIdResolver反过来处理ID魔术。在这个例子中,所有内容都是硬编码的,在实际代码中,它当然看起来更好。 :)

public class MyTypeIdResolver extends TypeIdResolverBase {

    @Override
    public String idFromValue(Object value) {
        return getId(value);
    }

    @Override
    public String idFromValueAndType(Object value, Class<?> suggestedType) {
        return getId(value);
    }

    @Override
    public JsonTypeInfo.Id getMechanism() {
        return JsonTypeInfo.Id.CUSTOM;
    }

    private String getId(Object value) {
        if (value instanceof ListWrapper.MyMapListWrapper) {
            return "MyMap[]";
        }

        if (value instanceof ListWrapper.Child1ListWrapper) {
            return "Child1[]";
        }

        if (value instanceof ListWrapper && !((ListWrapper) value).getValues().isEmpty()) {
            return ((ListWrapper) value).getValues().get(0).getClass().getSimpleName() + "[]";
        }

        return value.getClass().getSimpleName();
    }

    @Override
    public JavaType typeFromId(DatabindContext context, String id) throws IOException {
        if (id.endsWith("[]")) {
            if (id.startsWith("Child1")) {
                return TypeFactory.defaultInstance().constructParametricType(ListWrapper.class, Child1.class);
            }
            if (id.startsWith("MyMap")) {
                return TypeFactory.defaultInstance().constructSpecializedType(TypeFactory.unknownType(), ListWrapper.MyMapListWrapper.class);
            }
        }
        if (id.equals("Child1")) {
            return TypeFactory.defaultInstance().constructSpecializedType(TypeFactory.unknownType(), Child1.class);
        }
        if (id.equals("MyMap")) {
            return TypeFactory.defaultInstance().constructSpecializedType(TypeFactory.unknownType(), MyMap.class);
        }

        return TypeFactory.unknownType();
    }
}

为了能够处理{"@type: "...", "@values": ...}个列表,我有一个ListWrapper类和子类。 Todo:使用自定义反序列化逻辑重新实现它。

public class ListWrapper<T> {    
    @JsonProperty("@values")
    private List<T> values;

    public static class MyMapListWrapper extends ListWrapper<MyMap> {
    }

    public static class Child1ListWrapper extends ListWrapper<Child1> {
    }
}

可以跳过子类的创建,但随后会将类型信息添加到每个元素中。 java.lang.*课程当然没有类型信息。

模型是:

public class Parent {
    private String prop;
    private Child1 child1;
    private MyMap map;
}

public class Child1 {
    private int anInt;
}

测试代码:

@Test
public void shouldDoTheTrick() throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
    mapper.addMixIn(Object.class, ObjectMixin.class);

    Parent parent = new Parent("Hello", new Child1(-1), new MyMap() {{
        put("JustString", "JustValue");
        put("List_With_All_MyMaps", new ListWrapper.MyMapListWrapper(new ArrayList<MyMap>() {{
            add(new MyMap() {{
                put("Key", "Value");
                put("object", new Child1(2));
            }});
            add(new MyMap() {{
                put("Key", "Value");
            }});
        }}));
        put("List_With_All_Child1", new ListWrapper.Child1ListWrapper(new ArrayList<Child1>() {{
            add(new Child1(41));
            add(new Child1(42));
        }}));
    }});

    String valueAsString = mapper.writeValueAsString(parent);

    Parent deser = mapper.readValue(valueAsString, Parent.class);
    assertEquals(parent, deser);
}

JSON输出:

{
  "prop" : "Hello",
  "child1" : {
    "anInt" : -1
  },
  "map" : {
    "JustString" : "JustValue",
    "List_With_All_MyMaps" : {
      "@type" : "MyMap[]",
      "@values" : [ {
        "Key" : "Value",
        "object" : {
          "@type" : "Child1",
          "anInt" : 2
        }
      }, {
        "Key" : "Value"
      } ]
    },
    "List_With_All_Child1" : {
      "@type" : "Child1[]",
      "@values" : [ {
        "anInt" : 41
      }, {
        "anInt" : 42
      } ]
    }
  }
}

UPD:真正的实施示例https://github.com/sdl/dxa-web-application-java/commit/7a36a9598ac2273007806285ea4d854db1434ac5