具有“默认”对象类型的嵌套对象的动态反序列化

iaqfqrcu  于 2021-08-20  发布在  Java
关注(0)|答案(1)|浏览(349)

我想使用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);

这适用于顶级对象,但不适用于嵌套对象。例如,如果嵌套对象是特殊对象,它仍将作为公共对象反序列化。
我想做的是告诉jackson:“无论嵌套级别如何,如果objtype=special,请使用specialobject,否则请使用generalobject”。我研究了多态反序列化并尝试使用@jsonsubtypes,但无法正确设置此逻辑。如何确保特殊对象被反序列化到适当的类中,即使它们是嵌套的?

htzpubme

htzpubme1#

我首先从这个要点中获得灵感,并尝试用一种习惯来解决它 TypeIdResolver . 不幸的是,这存在无法反序列化 objType 正确(参见本答案的第一版)。
然后,我从这个答案中得到了灵感,转而选择了一种习惯 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);

相关问题