【问题标题】:Jackson JSON custom class instantiator for third-party class第三方类的 Jackson JSON 自定义类实例化器
【发布时间】:2021-01-06 17:33:58
【问题描述】:

我正在使用只能使用构建器创建的第三方 POJO 类 RetryOptions。只能使用静态方法 RetryOptions.newBuilder() 或通过在现有实例上调用 options.toBuilder() 来实例化构建器。

我想为第三方 POJO (RetryOptions) 创建自定义反序列化器。我的第一种方法是将对象编写为构建器,然后将对象读取为构建器并返回构建结果:

    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());

或者是否有另一种方法来解决这个问题,而不求助于我自己的反射逻辑或第三方类的暴力复制?

注意:我的解决方案必须使用 Jackson JSON 库(没有 Guava 等)
注意:这个第三方库中有几个类遇到了同样的问题,所以一个通用的解决方案很有帮助

【问题讨论】:

    标签: java jackson


    【解决方案1】:

    更新

    Jackson 可以反序列化私有字段,只要它们有一个 getter(参见 https://www.baeldung.com/jackson-field-serializable-deserializable-or-not)。

    所以,事实证明,在我的场景中,我不需要通过构建器反序列化 RetryOptions,我只需要能够构造一个 RetryOptions 的实例,Jackson 可以使用它来填充字段.

    由于我有多个具有相同约束的类(第三方类上没有公共构造函数),我编写了以下方法从 Supplier lambda 生成 ValueInstantiators

    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);
    }
    

    然后我为我的每个课程注册了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);
    

    不需要自定义反序列化器。

    原始回复

    我找到了办法。

    首先,为类定义一个ValueInstantiator。 Jackson 文档强烈建议您扩展 StdValueInstantiator

    在我的场景中,我只需要“默认”(无参数)实例化器,因此我覆盖了 canCreateUsingDefaultcreateUsingDefault 方法。

    如果需要,还有其他方法可以从参数创建。

        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);
    

    注意:我的解决方案使用问题中定义的反序列化器 - 即在序列化之前首先将 RetryOptions 实例转换为构建器,然后反序列化回构建器并构建到恢复实例。

    原始回复结束

    【讨论】:

    • 我们可以为类路径上的所有类注册这个自定义实例化器,还是可以通过对象映射器全局设置它? @戈登豆
    • @Adi - 我不太确定我是否理解你的问题。您是要覆盖每个对象的对象实例化,还是只覆盖其中的一部分?
    • 是的,每个对象。
    • 我不确定如何为每个对象执行此操作。听起来这可能是一个很好的问题!
    猜你喜欢
    • 2015-05-05
    • 2014-11-03
    • 1970-01-01
    • 2016-07-23
    • 2020-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-12
    相关资源
    最近更新 更多