首页 > 解决方案 > 具有兄弟类型的联合对象的杰克逊反序列化

问题描述

我正在尝试结合 Jackson 的一些功能,以便我可以将{type,value}json 中的一对反序列化为 java 中的 Union 类型,表示所有信息,但无法弄清楚如何去做。帮助将不胜感激。

这是我正在使用的内容:

  1. java Union 类型强制在任何时候只能设置一个值,将其视为枚举,但具有动态数据。
  2. 联合的值可以采用任意数量的类型,一些标量和一些对象或集合。
  3. 联合的多个选项可以共享相同的类型,即noyes并且在下面的示例中offon都是boolean类型。
  4. 我无法以任何方式控制 json 结构(它是传入/传出外部 API 的数据),因此根本无法更改它。
  5. Java 类是从 Thrift idl 生成的代码,因此我无法向它们添加注释或大幅调整它们的结构。虽然我控制了 idl,但希望保持它相当干净并且没有泄漏模式,例如{type, value}json 所需的泄漏模式,但不是强类型语言。
  6. json 中使用的类型名称(以及一些字段名称)与 java(和其他语言)关键字冲突,这就是每个值都带有后缀的原因:noyes -> noYesValue,float -> floatValue

一个具体的例子

我有一个看起来像这样的 json 文档:

{
  "obj": [
    {
      "type": "noyes",
      "value": true",
      "id": 1
    }, {
      "type": "offon",
      "value": false,
      "id": 2
    }, {
      "type": "text",
      "value": "hello",
      "id": 3
    }, {
      "type": "float",
      "value": 1.2,
      "id": 4
    }, {
      "type": "times",
      "value": [{"s": "12:22", "e": "16:00"}]
      "id": 5
    }
  ]
}

看起来像这样的java类:

class Response {
  List<Item> obj;
}

class Item {
  int id;
  Value value;
}

class Value {
  enum Fields { NO_YES_VALUE, OFF_ON_VALUE, TEXT_VALUE, FLOAT_VALUE, TIMES_VALUE }

  static Item noYesValue(boolean noYes) {...}
  static Item offOnValue(boolean offOn) {...}
  static Item textValue(String text) {...}
  static Item floatValue(float value) {...}
  static Item timesValue(List<TimesValue> times) {...}

  Fields setField;
  Object fieldValue;

  Value() {}
  Value(Fields field, Object value) {setFieldValue(field, value);}

  Fields getSetField() { return setField; }
  Object getFieldValue() { return fieldValue; }
  void setFieldValue(Fields field, Object value) {
    // checkType(field, value);
    this.setField = field;
    this.fieldValue = value;
  }

  // these do have checks for the set field, types, null, etc
  boolean getNoYesValue() { return (Boolean) fieldValue; }
  void setNoYesValue(boolean v) { setField = NO_YES_VALUE; fieldValue = v; }
  boolean getOffOnValue() { return (Boolean) fieldValue; }
  void setOffOnValue(boolean v) { setField = OFF_ON_VALUE; fieldValue = v; }
  // ...
}

class TimesValue {
  String startTime;
  String endTime;
}

对于 java 类,以下是等价的:

Value.noYesValue(false);
new Value().setNoYesValue(false);
new Value().setFieldValue(NO_YES_VALUE, false);
new Value(NO_YES_VALUE, false);

同样,以下是等价的:

value.getNoYesValue();
(Boolean) value.getFieldValue();

部分工作代码(做我想要的,但只适用于简单类型)

经过更多的挖掘和反复试验,我设法比以前走得更远。

首先,我把它Value弄平Item

interface ItemMixin {
  @JsonUnwrapped Value getValue();
}

这提升了我所有的Value属性,因此它们成为Item类的一部分,相当于{"value": {"type": "offon", "value": false}}成为{"type": "offon", "value": false}

接下来我需要处理那个Fields枚举,为此我编写了一个看起来像这样的自定义反序列化器并将它注册到一个模块

new StdDeserializer<Value.Fields>(Value.Fields.class) {
  @Override
  public Value.Fields deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    String type = p.getText();
    switch (type) {
      case "noyes": return Value.Fields.NO_YES_VALUE;
      case "offon": return Value.Fields.OFF_ON_VALUE;
      case "float": return Value.Fields.FLOAT_VALUE;
      case "text": return Value.Fields.TEXT_VALUE;
      case "times": return Value.Fields.TIMES_VALUE;
      // ...
    }
    ctxt.handleWeirdStringValue(Value.Fields.class, type, "Unsupported Value type");
    return null;
  }
}

最后我告诉Value类型使用构造函数来创建对象

static abstract class ValueMixin {
  @JsonCreator
  ValueMixin(@JsonProperty("type") Value.Fields type, @JsonProperty("value") Object value) {}
}

这给了我所需的 90%,但是我现在遇到的问题是这仅适用于原始类型(boolean,String等),我的times字段只是在我的代码中被反序列化为String导致异常。

我尝试在参数上使用@JsonTypeInfo和,为该字段创建一个属性并将注释放在那里,但无法让创建者正确解析所需的类型。@JsonSubTypesvalue

标签: javajsonjackson

解决方案


推荐阅读