当前位置: 首页 > 知识库问答 >
问题:

第三方类的Jackson JSON自定义类实例化器

彭博厚
2023-03-14

我使用的是只能使用生成器创建的第三方POJO类RetryOptions。生成器只能使用静态方法RetryOptions实例化。newBuilder(),或通过调用选项。现有实例上的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的实例。建筑商:

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

或者有没有其他方法可以在不诉诸我自己的反射逻辑或第三方类的暴力复制的情况下解决这个问题?

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

共有2个答案

阴宏爽
2023-03-14

如果您出于任何原因无法使用委托反序列化方法来最初创建对象,那么这里还涉及到另一种看似黑暗的魔力。为了让引用恢复工作,我使用了@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);
        }
    }

这里是反序列化的对象。注意:这使用委托对象生成器和解析器,使用上下文findObjectId创建可读对象id。从那里,我们将新创建的对象绑定到ReadableObjectId,以确保它稍后能够解析!

闾丘霖
2023-03-14

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

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

由于我有多个具有相同约束的类(第三方类上没有公共构造函数),我编写了以下方法来从供应商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);

不需要自定义反序列化程序

我找到了一个方法。

首先,为类定义一个值实例化器。Jackson文档强烈鼓励您扩展StdValueInstantiator。

在我的场景中,我只需要“default”(无参数)实例化器,因此我覆盖了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);

注意:我的解决方案使用了问题中定义的反序列化器,即先将RetryOptions实例转换为builder,然后再反序列化回builder和building以恢复实例。

原始答复结束

 类似资料:
  • JustAuth (opens new window)发展到现在,基本上已经涵盖了国内外大多数知名的网站。JustAuth (opens new window)也一直以它的全和简,备受各位朋友的厚爱、支持。 但现在OAuth技术越来越成熟,越来越多的个人站长或者企业都开始搭建自己的OAuth授权平台,那么针对这种情况,JustAuth (opens new window)并不能做到面面俱到,无法去

  • 我对Java有很好的理解,但我正在努力学习c来制作游戏,因为我认为c是一种更好的语言。但是我对c有一些问题。 是一个带有字符串构造函数的类,只是在这个类中声明它会导致错误。如果我删除的构造函数,我不会得到错误。 错误:render.cpp:3:16:错误:没有匹配的函数调用着色器::着色器()

  • 问题内容: 我的代码结构如下。 和 基本上我正在扫描网站以获取统计信息,例如标题标签,重复标题等。 我正在使用JQuery并向Web服务进行AJAX调用并检索url统计信息,而该过程正在运行以显示到目前为止收集的用户url统计信息,因为扫描大型网站需要花费大量时间。因此,每隔5秒,我就会从服务器检索统计信息。现在的问题是我需要在扫描处理完成时(而不是更新期间)最后发送所有List变量数据。现在发生

  • 问题内容: 我有一个名为“威士忌制造商”的课程,它只会启动新的威士忌。现在,我想在“ WhiskyOverViewController”中添加新添加的威士忌。但是我面临以下问题: 在“ stringArray”行中,出现错误“实例成员’whiskyArray’无法用于类型’WhiskyOverViewController’。为什么不能在那里使用whiskyArray变量? 在此先感谢您的帮助 问题

  • 在FastAdmin插件开发过程中经常需要引用于第三方的类库,此时可以采用以下两种方式进行引入。 手动修改文件命名空间 如果我们引入的第三方类库文件比较少,我们可以采取手动修改文件命名空间的功能。例如我们需要引入HashMap.php这个类库,这个类库功能比较单一,只有一个文件,此时我们可以将HashMap.php文件放在addons/mydemo/library目录下,然后再修改HashMap.

  • 本文向大家介绍Java自定义异常类的实例详解,包括了Java自定义异常类的实例详解的使用技巧和注意事项,需要的朋友参考一下 Java自定义异常类的实例详解 为什么要自己编写异常类?假如jdk里面没有提供的异常,我们就要自己写。我们常用的类ArithmeticException,NullPointerException,NegativeArraySizeException,ArrayIndexout