【问题标题】:Dynamic deserialization of nested objects with "default" object type具有“默认”对象类型的嵌套对象的动态反序列化
【发布时间】:2021-07-08 08:29:15
【问题描述】:

我想使用 Jackson 将对象动态反序列化为适当的 java 类,但我无法以正确的方式配置 Jackson。

我有以下简化模型(为简洁起见,省略了 getter/setter):

class GeneralObject {
    public String objType;
    public String commonProp;
    public GeneralObject nestedObject;

    // map for additional properties, so that I can re-serialize the full object later
    public Map<String, JsonNode> additionalFields = new HashMap<>();
    @JsonAnyGetter
    public Map<String, JsonNode> getAdditionalFields() {
        return additionalFields;
    }
    @JsonAnySetter
    public void addAdditionalField(String fieldName, JsonNode value) {
        this.additionalFields.put(fieldName, value);
    }
}

class SpecialObject extends GeneralObject {
    public String specialProp;
}

实际上,有各种各样的“特殊对象”,我想成为 将来需要时可以添加更多。

json 看起来像这样(我从外部来源获取它们,我无法更改 发送格式):

{
  "objType": "someType1",
  "commonProp": "example1..."
}
{
  "objType": "SPECIAL",
  "commonProp": "example2...",
  "specialProp": "more example"
}
{
  "objType": "someOtherType",
  "commonProp": "example3...",
  "nestedObject": {
    "objType": "SPECIAL",
    "commonProp": "example2...",
    "specialProp": "more example"
  }
}

我目前正在像这样解析它们:

ObjectMapper mapper = new ObjectMapper();
String objString = "{\"objType\": \"SPECIAL\", \"commonProp\": \"...\", \"specialProp\": \"more example\"}";
GeneralObject genObj = mapper.readValue(objString, GeneralObject.class);
if (genObj.objType.equals("SPECIAL")) {
    genObj = mapper.readValue(objString, SpecialObject.class);
}
// Some business-logic: If SPECIAL, then this cast is required to work:
System.out.println(((SpecialObject) genObj).specialProp);

这适用于顶级对象,但不适用于嵌套对象。如果,对于 例如,嵌套的 Object 是一个 SPECIAL 对象,它仍然会被反序列化为 一个共同的对象。

想要做的是告诉Jackson:“无论嵌套级别如何,如果objType=SPECIAL,使用SpecialObject,否则使用GeneralObject”。我查看了Polymorphic Deserialization 并尝试使用@JsonSubTypes,但无法正确设置此逻辑。如何确保 SPECIAL 对象被反序列化为适当的类,即使它们是嵌套的?

【问题讨论】:

    标签: java serialization jackson


    【解决方案1】:

    我首先从this Gist 中获得灵感,并尝试通过自定义TypeIdResolver 来解决它。不幸的是,这存在无法正确反序列化 objType 的问题(请参阅此答案的第一个版本)。

    然后我从this answer得到灵感,改用自定义Deserializer

    class CustomDeserializer extends StdDeserializer<GeneralObject> {
    
        private static final String SPECIAL = "\"SPECIAL\"";
    
        protected CustomDeserializer() {
            super(GeneralObject.class);
        }
    
        @Override
        public GeneralObject deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
            TreeNode node = p.readValueAsTree();
    
            // Select appropriate class based on "resourceType"
            TreeNode objTypeNode = node.get("objType");
            if (null == objTypeNode) {
                throw new JsonParseException(p, "field \"objType\" is missing!");
            }
            if (!objTypeNode.isValueNode()) {
                throw new JsonParseException(p, "field \"objType\" must be a String.");
            }
            String objType = objTypeNode.toString();
            Class<? extends GeneralObject> clazz;
            if (objType.equals(SPECIAL)) {
                clazz = SpecialObject.class;
            } else {
                clazz = RecursionStopper.class;
            }
            return p.getCodec().treeToValue(node, clazz);
        }
    }
    

    它检查.objType 的内容并发出应该用于反序列化的适当(子)类。反序列化器需要在GeneralObject 上注册,例如使用以下注解:

    @JsonDeserialize(using = CustomDeserializer.class)
    class GeneralObject {
        ...
    }
    

    为了阻止无限递归循环的发生,所有子类都必须注解不使用这个自定义反序列化器,我们需要引入一个帮助类来停止GeneralObject的递归:

    @JsonDeserialize(using = JsonDeserializer.None.class)
    class SpecialObject extends GeneralObject {
        public String specialProp;
    }
    
    @JsonDeserialize(using = JsonDeserializer.None.class)
    class RecursionStopper extends GeneralObject {
        // this class intentionally empty
    }
    
    

    反序列化按预期工作,也适用于嵌套对象:

    ObjectMapper mapper = new ObjectMapper();
    String objString = "{\n" +
            "  \"objType\": \"someObjType\",\n" +
            "  \"commonProp\": \"example3...\",\n" +
            "  \"nestedObject\": {\n" +
            "    \"objType\": \"SPECIAL\",\n" +
            "    \"commonProp\": \"example2...\",\n" +
            "    \"specialProp\": \"more example\"\n" +
            "  }\n" +
            "}";
    GeneralObject genObj = mapper.readValue(objString, GeneralObject.class);
    System.out.println(((SpecialObject) genObj.nestedObject).specialProp);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-08
      • 1970-01-01
      • 2015-05-30
      • 1970-01-01
      • 2018-09-21
      • 1970-01-01
      • 2020-02-07
      相关资源
      最近更新 更多