Jackson JSON 自定义 class 第三方实例化器 class
Jackson JSON custom class instantiator for third-party class
我正在使用只能使用生成器创建的第三方 POJO class RetryOptions
。构建器只能使用静态方法 RetryOptions.newBuilder()
或通过在现有实例上调用 options.toBuilder()
来实例化。
我想为第三方 POJO (RetryOptions
) 创建自定义 de/serializers。我的第一种方法是将对象编写为构建器,然后将对象作为构建器读取,然后 return 构建结果:
class RetryOptionsSerializer extends StdSerializer<RetryOptions> {
@Override
public void serialize(RetryOptions value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// Save as a builder
gen.writeObject(value.toBuilder());
}
}
class RetryOptionsDeserializer extends StdDeserializer<RetryOptions> {
@Override
public RetryOptions deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// Read as builder, then build
return p.readValueAs(RetryOptions.Builder.class).build();
}
}
但问题是 Jackson 不知道如何创建 RetryOptions.Builder
的实例来填充它的字段。
有没有一种方法可以指导 Jackson 如何创建构建器实例,但让 Jackson 处理字段的解析、反射和赋值?
可能是这样的:
class RetryOptionsDeserializer extends StdDeserializer<RetryOptions> {
@Override
public RetryOptions deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// Read as builder, then build
var builder = RetryOptions.newBuilder();
return p.readValueInto(builder).build();
}
}
或者也许有一种方法可以告诉对象映射器如何创建 RetryOptions.Builder
:
的实例
var mapper = new ObjectMapper();
mapper.registerValueInstantiator(RetryOptions.Builder, () -> RetryOptions.newBuilder());
或者有没有另一种方法来解决这个问题,而不是诉诸我自己的反射逻辑或第三方的暴力复制class?
注意:我的解决方案必须使用 Jackson JSON 库(没有 Guava 等)
注意:在这个第三方库中有几个 classes 运行 属于同一个问题,所以通用的解决方案是有帮助的
更新
Jackson 可以反序列化私有字段,只要它们具有 getter(参见 https://www.baeldung.com/jackson-field-serializable-deserializable-or-not)。
所以,事实证明,在我的场景中,我不需要通过构建器反序列化 RetryOptions
,我只需要能够构造 Jackson 的 RetryOptions
实例可用于填充字段。
由于我有多个具有相同约束的 classes(第三方 class 上没有 public 构造函数),我编写了以下方法来生成 ValueInstantiators
来自 Supplier
lambda:
static ValueInstantiator createDefaultValueInstantiator(DeserializationConfig config, JavaType valueType, Supplier<?> creator) {
class Instantiator extends StdValueInstantiator {
public Instantiator(DeserializationConfig config, JavaType valueType) {
super(config, valueType);
}
@Override
public boolean canCreateUsingDefault() {
return true;
}
@Override
public Object createUsingDefault(DeserializationContext ctxt) {
return creator.get();
}
}
return new Instantiator(config, valueType);
}
然后我为每个 class 注册了 ValueInstantiators
,例如:
var mapper = new ObjectMapper();
var module = new SimpleModule()
.addValueInstantiator(
RetryOptions.class,
createDefaultValueInstantiator(
mapper.getDeserializationConfig(),
mapper.getTypeFactory().constructType(RetryOptions.class),
() -> RetryOptions.newBuilder().validateBuildWithDefaults()
)
)
.addValueInstantiator(
ActivityOptions.class,
createDefaultValueInstantiator(
mapper.getDeserializationConfig(),
mapper.getTypeFactory().constructType(ActivityOptions.class),
() -> ActivityOptions.newBuilder().validateAndBuildWithDefaults()
)
);
mapper.registerModule(module);
不需要自定义 de/serializers。
原始回复
我找到方法了。
首先,为class定义一个ValueInstantiator
。 Jackson 文档强烈建议您扩展 StdValueInstantiator
.
在我的场景中,我只需要“默认”(无参数)实例化器,所以我覆盖了 canCreateUsingDefault
和 createUsingDefault
方法。
如果需要,还有其他方法可以从参数创建。
class RetryOptionsBuilderValueInstantiator extends StdValueInstantiator {
public RetryOptionsBuilderValueInstantiator(DeserializationConfig config, JavaType valueType) {
super(config, valueType);
}
@Override
public boolean canCreateUsingDefault() {
return true;
}
@Override
public Object createUsingDefault(DeserializationContext ctxt) throws IOException {
return RetryOptions.newBuilder();
}
}
然后我用 ObjectMapper
:
注册我的 ValueInstantiator
var mapper = new ObjectMapper();
var module = new SimpleModule();
module.addDeserializer(RetryOptions.class, new RetryOptionsDeserializer());
module.addSerializer(RetryOptions.class, new RetryOptionsSerializer());
module.addValueInstantiator(
RetryOptions.Builder.class,
new RetryOptionsBuilderValueInstantiator(
mapper.getDeserializationConfig(),
mapper.getTypeFactory().constructType(RetryOptions.Builder.class))
);
mapper.registerModule(module);
现在我可以像这样反序列化 RetryOptions
的实例:
var options = RetryOptions.newBuilder()
.setInitialInterval(Duration.ofMinutes(1))
.setMaximumAttempts(7)
.setBackoffCoefficient(1.0)
.build();
var json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(options);
var moreOptions = mapper.readValue(json, RetryOptions.class);
注意:我的解决方案使用了问题中定义的de/serializers——即在序列化之前首先将RetryOptions
实例转换为构建器,然后反序列化回构建器并构建以恢复实例。
原始回复结束
如果您出于某种原因无法使用委托反序列化方法来初始创建对象,那么这里还涉及到另一个看似黑暗的魔法。为了恢复引用的工作,我使用了@GordonBean 描述的 DelegatingDeserializer
,但必须添加:
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
final EntityCreateSpec spec = ctxt.readValue(p, EntityCreateSpec.class);
try {
final Entity entity = engine.createEntity(
(Class<? extends Entity>) ctxt.findClass(spec.getClazz()),
spec.getId(),
spec.getComponent());
// The Important Bit
// Bind entities for back references.
final ObjectIdReader reader = this.getObjectIdReader();
final ReadableObjectId roid = ctxt.findObjectId(entity.getId(), reader.generator, reader.resolver);
roid.bindItem(entity);
// End Important Bit
return entity;
} catch (ClassNotFoundException | FactoryException e) {
throw new RuntimeException("Unable to deserialize " + spec.getClazz(), e);
}
}
其中 entity
这里是被反序列化的东西。注意:这使用委托 ObjectIdReader
来获取生成器和解析器,使用上下文 findObjectId 创建 ReadableObjectId
。从那里,我们将新创建的对象绑定到 ReadableObjectId
以确保它稍后会解析!
我正在使用只能使用生成器创建的第三方 POJO class RetryOptions
。构建器只能使用静态方法 RetryOptions.newBuilder()
或通过在现有实例上调用 options.toBuilder()
来实例化。
我想为第三方 POJO (RetryOptions
) 创建自定义 de/serializers。我的第一种方法是将对象编写为构建器,然后将对象作为构建器读取,然后 return 构建结果:
class RetryOptionsSerializer extends StdSerializer<RetryOptions> {
@Override
public void serialize(RetryOptions value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// Save as a builder
gen.writeObject(value.toBuilder());
}
}
class RetryOptionsDeserializer extends StdDeserializer<RetryOptions> {
@Override
public RetryOptions deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// Read as builder, then build
return p.readValueAs(RetryOptions.Builder.class).build();
}
}
但问题是 Jackson 不知道如何创建 RetryOptions.Builder
的实例来填充它的字段。
有没有一种方法可以指导 Jackson 如何创建构建器实例,但让 Jackson 处理字段的解析、反射和赋值?
可能是这样的:
class RetryOptionsDeserializer extends StdDeserializer<RetryOptions> {
@Override
public RetryOptions deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// Read as builder, then build
var builder = RetryOptions.newBuilder();
return p.readValueInto(builder).build();
}
}
或者也许有一种方法可以告诉对象映射器如何创建 RetryOptions.Builder
:
var mapper = new ObjectMapper();
mapper.registerValueInstantiator(RetryOptions.Builder, () -> RetryOptions.newBuilder());
或者有没有另一种方法来解决这个问题,而不是诉诸我自己的反射逻辑或第三方的暴力复制class?
注意:我的解决方案必须使用 Jackson JSON 库(没有 Guava 等)
注意:在这个第三方库中有几个 classes 运行 属于同一个问题,所以通用的解决方案是有帮助的
更新
Jackson 可以反序列化私有字段,只要它们具有 getter(参见 https://www.baeldung.com/jackson-field-serializable-deserializable-or-not)。
所以,事实证明,在我的场景中,我不需要通过构建器反序列化 RetryOptions
,我只需要能够构造 Jackson 的 RetryOptions
实例可用于填充字段。
由于我有多个具有相同约束的 classes(第三方 class 上没有 public 构造函数),我编写了以下方法来生成 ValueInstantiators
来自 Supplier
lambda:
static ValueInstantiator createDefaultValueInstantiator(DeserializationConfig config, JavaType valueType, Supplier<?> creator) {
class Instantiator extends StdValueInstantiator {
public Instantiator(DeserializationConfig config, JavaType valueType) {
super(config, valueType);
}
@Override
public boolean canCreateUsingDefault() {
return true;
}
@Override
public Object createUsingDefault(DeserializationContext ctxt) {
return creator.get();
}
}
return new Instantiator(config, valueType);
}
然后我为每个 class 注册了 ValueInstantiators
,例如:
var mapper = new ObjectMapper();
var module = new SimpleModule()
.addValueInstantiator(
RetryOptions.class,
createDefaultValueInstantiator(
mapper.getDeserializationConfig(),
mapper.getTypeFactory().constructType(RetryOptions.class),
() -> RetryOptions.newBuilder().validateBuildWithDefaults()
)
)
.addValueInstantiator(
ActivityOptions.class,
createDefaultValueInstantiator(
mapper.getDeserializationConfig(),
mapper.getTypeFactory().constructType(ActivityOptions.class),
() -> ActivityOptions.newBuilder().validateAndBuildWithDefaults()
)
);
mapper.registerModule(module);
不需要自定义 de/serializers。
原始回复
我找到方法了。
首先,为class定义一个ValueInstantiator
。 Jackson 文档强烈建议您扩展 StdValueInstantiator
.
在我的场景中,我只需要“默认”(无参数)实例化器,所以我覆盖了 canCreateUsingDefault
和 createUsingDefault
方法。
如果需要,还有其他方法可以从参数创建。
class RetryOptionsBuilderValueInstantiator extends StdValueInstantiator {
public RetryOptionsBuilderValueInstantiator(DeserializationConfig config, JavaType valueType) {
super(config, valueType);
}
@Override
public boolean canCreateUsingDefault() {
return true;
}
@Override
public Object createUsingDefault(DeserializationContext ctxt) throws IOException {
return RetryOptions.newBuilder();
}
}
然后我用 ObjectMapper
:
ValueInstantiator
var mapper = new ObjectMapper();
var module = new SimpleModule();
module.addDeserializer(RetryOptions.class, new RetryOptionsDeserializer());
module.addSerializer(RetryOptions.class, new RetryOptionsSerializer());
module.addValueInstantiator(
RetryOptions.Builder.class,
new RetryOptionsBuilderValueInstantiator(
mapper.getDeserializationConfig(),
mapper.getTypeFactory().constructType(RetryOptions.Builder.class))
);
mapper.registerModule(module);
现在我可以像这样反序列化 RetryOptions
的实例:
var options = RetryOptions.newBuilder()
.setInitialInterval(Duration.ofMinutes(1))
.setMaximumAttempts(7)
.setBackoffCoefficient(1.0)
.build();
var json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(options);
var moreOptions = mapper.readValue(json, RetryOptions.class);
注意:我的解决方案使用了问题中定义的de/serializers——即在序列化之前首先将RetryOptions
实例转换为构建器,然后反序列化回构建器并构建以恢复实例。
原始回复结束
如果您出于某种原因无法使用委托反序列化方法来初始创建对象,那么这里还涉及到另一个看似黑暗的魔法。为了恢复引用的工作,我使用了@GordonBean 描述的 DelegatingDeserializer
,但必须添加:
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
final EntityCreateSpec spec = ctxt.readValue(p, EntityCreateSpec.class);
try {
final Entity entity = engine.createEntity(
(Class<? extends Entity>) ctxt.findClass(spec.getClazz()),
spec.getId(),
spec.getComponent());
// The Important Bit
// Bind entities for back references.
final ObjectIdReader reader = this.getObjectIdReader();
final ReadableObjectId roid = ctxt.findObjectId(entity.getId(), reader.generator, reader.resolver);
roid.bindItem(entity);
// End Important Bit
return entity;
} catch (ClassNotFoundException | FactoryException e) {
throw new RuntimeException("Unable to deserialize " + spec.getClazz(), e);
}
}
其中 entity
这里是被反序列化的东西。注意:这使用委托 ObjectIdReader
来获取生成器和解析器,使用上下文 findObjectId 创建 ReadableObjectId
。从那里,我们将新创建的对象绑定到 ReadableObjectId
以确保它稍后会解析!