我正在寻找一种使用抽象父类(super class)上的 @JsonDeserialize
注释注册的反序列化器来反序列化子类的方法。如果有更好的选择,我很乐意调整这些选项 - 但目前我不知道有任何解决方案可以解决此问题。
核心问题是:有一个抽象父类(super class)A
:
@JsonSerialize(using = SerializerForA.class)
@JsonDeserialize(using = DeserializerForA.class)
public abstract class A {
private String value;
protected A(String value) {
this.value = value;
}
...
}
(注释是我尝试进行自定义反序列化的尝试 - 也许这是错误的方法)。
有一些派生类,而 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
)?
如果有帮助的话,可以通过一些方法来更改代码。例如,setter 可以用于 value
字段,而不是构造函数,但我更喜欢构造函数或某些工厂方法 (public static A getInstance(String value) { . .. }
).
编辑 (1) 反序列化器应该由 ObjectMapper
自动调用,无需任何特定代码,例如:
ObjectMapper mapper = new ObjectMapper();
MyClass myClass = mapper.readValue(json, MyClass.class);
这意味着 Jackson 知道容器类的类型。它还知道属性 a
和 b
的类型。
使用自定义反序列化器的原因是我需要控制实例创建过程(基本上,我想为相同的值重用每个对象的相同实例 - 类似于枚举)。
编辑 (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 而不会崩溃。
最佳答案
综上所述,ObjectMapper
提供了 MyClass
的实例包含一个B
和一个C
.
jackson 将调用 JsonDeserializer<A>
两者都是B
和C
提供字符串 "value_of_b"
/"value_of_c"
(因为通过反射,它会知道 B
和 C
是 A
的实例,并且这是上下文中唯一可用的反序列化器)。
考虑到在 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);
}
}
所以基本上每个实现都会为您提供String value
您需要创建一个 A
,当然,您必须创建 A
的具体基本实现在您这边以便实例化它(因为您不知道其他实现是什么,并且因为您需要它具体来创建实例)。
关于java - 获取反序列化器中的属性类型( jackson ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72192825/