【问题标题】:Jackson polymorphic deserialization with dynamic types具有动态类型的 Jackson 多态反序列化
【发布时间】:2017-01-26 15:40:55
【问题描述】:

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

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> {
}

(省略访问器)

基本上我需要一种 Data Binder,Jackson 每次尝试解析任何字段保存上下文的类型时都会询问一个类型,如果这个数据绑定器没有找到任何特定于应用程序的东西,Jackson 应该回退到默认值类型解析。

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

【问题讨论】:

  • 请不要在 Stack Overflow 上的问题中发布代码链接。
  • 试试ObjectMapper也许对你有用。

标签: java json jackson


【解决方案1】:

在与杰克逊一起玩了一段时间后,我得出了以下解决方案。适合我。

首先我们让一切都多态

@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

【讨论】:

    猜你喜欢
    • 2014-05-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-22
    • 2021-11-08
    • 1970-01-01
    • 1970-01-01
    • 2015-02-05
    • 1970-01-01
    相关资源
    最近更新 更多