本文介绍了第三方类的Jackson JSON自定义类实例化器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am using a third-party POJO class RetryOptions that can only be created using a builder. The builder can only be instantiated using a static method RetryOptions.newBuilder(), or by calling options.toBuilder() on an existing instance.

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

I would like to create custom de/serializers for the third-party POJO (RetryOptions). My first approach was to write the object as a builder, then read the object as a builder and return the built result:

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

但是问题在于,杰克逊不知道如何创建RetryOptions.Builder的实例来填充其字段.

But the problem is that Jackson doesn't know how to create an instance of RetryOptions.Builder in order to populate it's fields.

有没有一种方法可以指导Jackson如何创建构建器实例,但是让Jackson处理字段的解析,反射和分配?

Is there a way I can instruct Jackson in how to create the builder instance, but let Jackson handle the parsing, reflection, and assignment of the fields?

也许是这样的:

    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的实例:

Or perhaps there is a way to tell the object mapper how to create an instance of RetryOptions.Builder:

var mapper = new ObjectMapper();
mapper.registerValueInstantiator(RetryOptions.Builder, () -> RetryOptions.newBuilder());

还是有另一种方法可以解决此问题而无需依靠我自己的反射逻辑或第三方类的强力复制?

Or is there another way to slice this problem without resorting to my own reflection logic or a brute-force duplication of the third-party class?

注意:我的解决方案必须使用 Jackson JSON库(不能使用Guava等).
注意:该第三方库中有多个类会遇到相同的问题,因此通用的解决方案会有所帮助

Note: my solution must use the Jackson JSON library (no Guava, etc.)
Note: there are several classes in this third party library that run into this same issue, so a generic solution is helpful

推荐答案

更新

只要私有字段具有吸气剂,杰克逊就可以反序列化私有字段(请参见 https://www.baeldung.com/jackson-field-serializable-deserializable-or-not ).

因此,在我的情况下,我不需要通过生成器反序列化RetryOptions,我只需要能够构造RetryOptions的实例,杰克逊就可以使用该实例来填充字段

So, it turns out, in my scenario, that I don't need to deserialize RetryOptions through the builder, I just need to be able to construct an instance of RetryOptions that Jackson can use to populate the fields.

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

As I had multiple classes with this same constraint (no public constructors on a third-party class), I wrote the following method to generate ValueInstantiators from a 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);
}

然后我为每个班级注册了ValueInstantiators,例如:

Then I registered ValueInstantiators for each of my classes, e.g:

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.

First, define a ValueInstantiator for the class. The Jackson documentation strongly encourages you to extend StdValueInstantiator.

在我的情况下,我只需要"default"(默认) (无参数)实例化程序,因此我覆盖了canCreateUsingDefaultcreateUsingDefault方法.

In my scenario, I only needed the "default" (parameter-less) instantiator, so I overrode the canCreateUsingDefault and createUsingDefault methods.

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

There are other methods for creating from arguments if needed.

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

然后我将ValueInstantiator注册到ObjectMapper:

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的实例:

Now I can deserialize an instance of RetryOptions like so:

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实例转换为构建器,然后再进行序列化,然后反序列化回构建器并构建为恢复实例.

Note: my solution makes use of the de/serializers defined in the question - i.e. that first convert the RetryOptions instance to a builder before serializing, then deserializing back to a builder and building to restore the instance.

原始回复结束

这篇关于第三方类的Jackson JSON自定义类实例化器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-22 15:54