获取反序列化程序中的属性类型(Jackson)

vom3gejh  于 2022-11-08  发布在  其他
关注(0)|答案(3)|浏览(205)

我正在寻找一种方法来使用一个反序列化器来反序列化一个子类,这个反序列化器是使用抽象超类上的@JsonDeserialize注解来注册的。如果有更好的选择,我很乐意采用这些选择--但是我目前还不知道这个问题的任何解决方案。
核心问题是:有一个抽象超类A

@JsonSerialize(using = SerializerForA.class)
@JsonDeserialize(using = DeserializerForA.class)
public abstract class A {
  private String value;

  protected A(String value) {
    this.value = value;
  }
  ...
}

(The注解是我进行自定义反序列化的尝试--也许这是一种错误的方法)。
有一些派生类,A不知道任何一个派生类。假设A是框架的一部分,派生类是使用框架的客户端代码。下面是两个派生类:

public class B extends A {
  public B(String value) {
    super(value);
  }
  ...
}

public class C extends A {
  public C(String value) {
    super(value);
  }
  ...
}

这些派生类在“容器”类中使用,例如:

public class MyClass {
  private B b;
  private C c;

  ...
}

对应的JSON如下所示:

{
  "b": "value_of_b",
  "c": "value_of_c"
}

编写序列化程序相对简单:

public class SerializerForA extends JsonSerializer<A> {
  @Override
  public void serialize(A obj, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    gen.writeString(obj.getValue());
  }
}

反序列化程序将如下所示:

public class DeserializerForA extends JsonDeserializer<A> {
  @Override
  public A deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
    A result = ???
    return result;
  }
}

但是反序列化程序如何知道结果对象的类型?是否可以从其中一个参数(例如DeserializationContext)获取类名?

如果有帮助的话,有一些方法可以改变代码。例如,可以对value字段使用setter,而不是构造函数,但我更喜欢构造函数,或者一些工厂方法(public static A getInstance(String value) { ... })。

**Edit(1)**反序列化程序应该由ObjectMapper自动调用,而不需要任何特定代码,如下所示:

ObjectMapper mapper = new ObjectMapper();
MyClass myClass = mapper.readValue(json, MyClass.class);

这意味着,Jackson知道容器类的类型,它也知道属性ab的类型。使用自定义反序列化器的原因是我需要对示例创建过程进行控制(基本上,我想为相同的值重用每个对象的相同示例-类似于枚举)。

**Edit(2)**修改JSON结构不是一个选项。但我认为没有必要。如果我不需要控制示例的创建,整个实现将直接工作。JSON中的额外类型信息应该是不必要的。
**编辑(3)**所有这些都是为了实现一个框架,应用程序可以使用该框架创建存储为JSON的类型安全对象。通常,我会使用Java enum来实现此目的,但客户端可能需要读取由新版本的框架创建的JSON文档。(使用新值),但客户端尚未更新框架版本。

示例:
有一个名为Currency的类:

public class Currency extends A {
  public static final Currency EUR = new Currency("EUR");
}

它是这样使用的:

public class Transaction {
  private Currency currency;
  private double amount;
}

JSON看起来像这样:

{
  "currency": "EUR",
  "amount": 24.34
}

现在又增加了一种新的货币:

public class Currency extends A {
  public static final Currency EUR = new Currency("EUR");
  public static final Currency USD = new Currency("USD");
}

使用新框架的客户端可以生成以下JSON:

{
  "currency": "USD",
  "amount": 48.93,
}

一个客户端没有更新到新的框架版本。这个客户端应该能够读取JSON而不会崩溃。

bttbmeg0

bttbmeg01#

总之,ObjectMapper具有MyClass的示例,该示例包含一个B和一个C
Jackson将为BC调用JsonDeserializer<A>,提供字符串"value_of_b"/"value_of_c"(因为通过反射,它将知道BCA的示例,并且这是上下文中唯一可用的反序列化器)。
考虑到在Jackson反序列化器中,您处于静态上下文中(这里没有A的任何具体示例,只是反序列化了一些字符串文本,其中包含的信息允许您创建MyClass的新示例,该示例看起来与他们提供给您的序列化示例类似),那么我认为您唯一的选择就是在代码中的某个位置创建一个工厂方法(我会直接在A类中创建它):

public static A getInstance(String value) {
    ...
}

然后在反序列化器中,根据序列化的示例是B还是C(因为最终,您只知道A,因此无法处理其他任何内容),简单地从该示例对其进行示例化:

public final class ADeserializer extends JsonDeserializer<A> {
    @Override
    public A deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        String value = jsonParser.getText();
        return A.getInstance(value);
    }
}

所以基本上每个实现都将为您提供创建A所需的String value,当然,您将不得不在您的一侧创建A的具体基本实现,以便对其进行示例化(因为您不知道其他实现是什么,并且因为您需要它是具体的,以便创建示例)。

u5i3ibmn

u5i3ibmn2#

在序列化过程中,您必须在json中包含一些信息。有两种方法可以实现这一点。
首先是启用默认类型。这将把类名添加到json中。它看起来像这样:

{
  "a": [
    "A",
    {
      "value": "a"
    }
  ],
  "b": [
    "B",
    {
      "value": "b"
    }
  ]
}

您可以通过调用activateDefaultTyping(ptv, DefaultTyping.OBJECT_AND_NON_CONCRETE)ObjectMapper上启用它
第二个是添加每个类的注解,你可以通过将这些注解添加到抽象类中来实现。

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "type")
@JsonSubTypes({ 
  @Type(value = A.class, name = "a"), 
  @Type(value = B.class, name = "b") 
})

然后,序列化器将生成如下所示的json:

{
  "a": {
    "type": "a",
    "value": "value_of_a"
  }
  "b": {
    "type": "b",
    "value": "value_of_b"
  }
}
vlf7wbxs

vlf7wbxs3#

一个简单的解决方案--甚至不需要太多的魔法--是使用工厂方法和@JsonCreator
基类和序列化程序都是已知的:

@JsonSerialize(using = SerializerForA.class)
  public class A {
    protected String value;

    public String getValue() {
      return value;
    }
  }

  public class SerializerForA extends JsonSerializer<A> {
    @Override
    public void serialize(A a, JsonGenerator gen, SerializerProvider serializers)
        throws IOException {
      gen.writeString(a.getValue());
    }
  }

每个继承的类都需要实现一个工厂方法:

public class B extends A {
    @JsonCreator
    public static B create(String value) {
      B b = new B();
      b.value = value;
      return b;
    }
  }

public class C extends A {
    @JsonCreator
    public static C create(String value) {
      C c = new C();
      c.value = value;
      return c;
    }
  }

现在,以下JSON解析成功:

{
  "b":"This is B",
  "c":"This is C"
}

明显的缺点是,继承的类必须实现工厂方法。我想避免这种情况。

相关问题