【问题标题】:How do I deserialize JSON documents into pre-processed beans in Jackson?如何将 JSON 文档反序列化为 Jackson 中的预处理 bean?
【发布时间】:2020-06-26 20:58:32
【问题描述】:

我正在尝试弄清楚如何将我在项目中使用的代码生成器和 Jackson 结合起来,以便将它们结合起来。 第三方 bean 代码生成器做了一些我想改进的事情。 比如下面的类

public class Wrapper {

    public String string;
    public List<String> array;

}

没有为stringarray 设置默认值。 在某些情况下(主要是由于 heavy 遗留原因),如果输入 JSON 文档中未提供默认值,我希望 Jackson 使用设置的默认值反序列化上述 bean 类。 例如,我希望将 {"string": "foo"} 反序列化为一个 bean,就好像源 JSON 是 {"string":"foo","array":[]} 这样它会产生一个具有两个非空字段的 bean。 我想到的第一个想法是创建一个 bean 实例,然后运行“设置默认字段”预处理器,然后将 JSON 读入构造和初始化的 bean。

public final class DefaultsModule
        extends SimpleModule {

    @Override
    public void setupModule(final SetupContext setupContext) {
        setupContext.addBeanDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(final DeserializationConfig config, final BeanDescription description,
                    final JsonDeserializer<?> defaultDeserializer) {
                return DefaultFieldsJsonDeserializer.create(description.getType(), description);
            }
        });
    }

    private static final class DefaultFieldsJsonDeserializer<T>
            extends JsonDeserializer<T> {

        // the generated classes set is finite, so won't bother with subclassing
        private static final Map<Class<?>, Supplier<?>> NEW_INSTANCES = new ImmutableMap.Builder<Class<?>, Supplier<?>>()
                .put(Iterable.class, ArrayList::new)
                .put(Collection.class, ArrayList::new)
                .put(List.class, ArrayList::new)
                .put(ArrayList.class, ArrayList::new)
                .put(LinkedList.class, LinkedHashMap::new)
                .put(Map.class, LinkedHashMap::new)
                .put(HashMap.class, HashMap::new)
                .put(LinkedHashMap.class, LinkedHashMap::new)
                .put(TreeMap.class, TreeMap::new)
                .put(Set.class, LinkedHashSet::new)
                .put(HashSet.class, HashSet::new)
                .put(LinkedHashSet.class, LinkedHashSet::new)
                .put(TreeSet.class, TreeSet::new)
                .build();

        private final BeanDescription description;
        private final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain;

        private DefaultFieldsJsonDeserializer(final BeanDescription description,
                final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain) {
            this.description = description;
            this.fieldDefaultsChain = fieldDefaultsChain;
        }

        private static <T> JsonDeserializer<T> create(final JavaType javaType, final BeanDescription description) {
            final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain = Stream.of(javaType.getRawClass().getDeclaredFields())
                    .filter(field -> NEW_INSTANCES.containsKey(field.getType()))
                    .peek(field -> field.setAccessible(true))
                    .map(field -> new AbstractMap.SimpleImmutableEntry<Field, Supplier<Object>>(field, () -> NEW_INSTANCES.get(field.getType()).get()))
                    .collect(Collectors.toList());
            return new DefaultFieldsJsonDeserializer<>(description, fieldDefaultsChain);
        }

        @Override
        @Nullable
        public T deserialize(final JsonParser parser, final DeserializationContext context)
                throws IOException {
            try {
                // instantiate the bean
                @Nullable
                @SuppressWarnings("unchecked")
                final T bean = (T) description.instantiateBean(false);
                if ( bean == null ) {
                    return null;
                }
                // do default values pre-processing
                for ( final Map.Entry<Field, ? extends Supplier<?>> e : fieldDefaultsChain ) {
                    final Field field = e.getKey();
                    final Object defaultValue = e.getValue().get();
                    field.set(bean, defaultValue);
                }
                // since the object is constructed and initialized properly, simply update it
                final ObjectReader objectReader = ((ObjectMapper) parser.getCodec())
                        .readerForUpdating(bean);
                return objectReader.readValue(parser);
            } catch ( final IllegalAccessException ex ) {
                return context.reportBadTypeDefinition(description, ex.getMessage());
            }
        }

    }

}

简而言之,我希望通过以下单元测试:

public final class DefaultsModuleTest {

    @NoArgsConstructor
    @AllArgsConstructor
    @EqualsAndHashCode
    private static final class Generated {

        @JsonProperty
        private String string;

        @JsonProperty
        private List<String> array /*not generated but should be initialized in the pre-processor = new ArrayList<>()*/;

    }

    @Test
    public void test()
            throws IOException {
        final ObjectMapper objectMapper = new ObjectMapper()
                .registerModule(new DefaultsModule());
        final Generated expected = new Generated("foo", Collections.emptyList());
        Assertions.assertEquals(expected, objectMapper.readValue("{\"string\":\"foo\"}", Generated.class));
        Assertions.assertEquals(expected, objectMapper.readValue("{\"string\":\"foo\",\"array\":null}", Generated.class));
        Assertions.assertEquals(expected, objectMapper.readValue("{\"string\":\"foo\",\"array\":[]}", Generated.class));
    }

}

不幸的是,上面的反序列化器在无限递归循环中运行。 所以我有多个问题:

  • 如何正确实现?
  • 也许我应该以某种方式使用ValueInstantiator
  • 获取委托 JSON 反序列化器的通用方法是什么? (Gson 允许在类型适配器工厂中获取委托类型适配器,Jackson 提供了反序列化器修饰符方法,但修饰符中的 JsonDeserializer 会导致奇怪的异常 + 不确定它是否可以更新现有对象。

我的 Jackson 数据绑定版本是 2.9.10。

【问题讨论】:

    标签: java json jackson


    【解决方案1】:

    我似乎已经意识到必须正确完成它的方式。我没有注意到我可以将上面提到的值实例化器添加到模块设置上下文中。配置完成后,我根本不需要创建自定义反序列化器,因为我可以自己提供构造+初始化的值。

    public final class DefaultsModule
            extends SimpleModule {
    
        @Override
        public void setupModule(final SetupContext setupContext) {
            setupContext.addValueInstantiators((config, description, defaultInstantiator) -> DefaultFieldsInstantiator.isSupported(description.getBeanClass())
                    ? DefaultFieldsInstantiator.create(config, description)
                    : defaultInstantiator
            );
        }
    
        private static final class DefaultFieldsInstantiator
                extends StdValueInstantiator {
    
            private static final Map<Class<?>, Supplier<?>> NEW_INSTANCES = new ImmutableMap.Builder<Class<?>, Supplier<?>>()
                    .put(Iterable.class, ArrayList::new)
                    .put(Collection.class, ArrayList::new)
                    .put(List.class, ArrayList::new)
                    .put(ArrayList.class, ArrayList::new)
                    .put(LinkedList.class, LinkedHashMap::new)
                    .put(Map.class, LinkedHashMap::new)
                    .put(HashMap.class, HashMap::new)
                    .put(LinkedHashMap.class, LinkedHashMap::new)
                    .put(TreeMap.class, TreeMap::new)
                    .put(Set.class, LinkedHashSet::new)
                    .put(HashSet.class, HashSet::new)
                    .put(LinkedHashSet.class, LinkedHashSet::new)
                    .put(TreeSet.class, TreeSet::new)
                    .build();
    
            private final BeanDescription description;
            private final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain;
    
            private DefaultFieldsInstantiator(final DeserializationConfig config, final BeanDescription description,
                    final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain) {
                super(config, description.getType());
                this.description = description;
                this.fieldDefaultsChain = fieldDefaultsChain;
            }
    
            private static boolean isSupported(final Class<?> clazz) {
                return ...............;
            }
    
            private static ValueInstantiator create(final DeserializationConfig config, final BeanDescription description) {
                final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain = Stream.of(description.getType().getRawClass().getDeclaredFields())
                        .filter(field -> NEW_INSTANCES.containsKey(field.getType()))
                        .peek(field -> field.setAccessible(true))
                        .map(field -> new AbstractMap.SimpleImmutableEntry<Field, Supplier<Object>>(field, () -> NEW_INSTANCES.get(field.getType()).get()))
                        .collect(Collectors.toList());
                return new DefaultFieldsInstantiator(config, description, fieldDefaultsChain);
            }
    
            @Override
            public boolean canCreateUsingDefault() {
                return true;
            }
    
            @Override
            @Nullable
            public Object createUsingDefault(final DeserializationContext context)
                    throws JsonMappingException {
                try {
                    @Nullable
                    final Object bean = description.instantiateBean(false);
                    if ( bean == null ) {
                        return null;
                    }
                    for ( final Map.Entry<Field, ? extends Supplier<?>> e : fieldDefaultsChain ) {
                        final Field field = e.getKey();
                        final Object defaultValue = e.getValue().get();
                        field.set(bean, defaultValue);
                    }
                    return bean;
                } catch ( final IllegalAccessException ex ) {
                    return context.reportBadDefinition(description.getType(), "Cannot set field: " + ex.getMessage());
                }
            }
    
        }
    
    }
    

    以下所有测试均通过:

    final ObjectMapper objectMapper = new ObjectMapper()
            .registerModule(new DefaultsModule());
    Assertions.assertNull(objectMapper.readValue("null", Generated.class));
    Assertions.assertEquals(new Generated(null, Collections.emptyList()), objectMapper.readValue("{}", Generated.class));
    Assertions.assertEquals(new Generated(null, ImmutableList.of("bar")), objectMapper.readValue("{\"array\":[\"bar\"]}", Generated.class));
    Assertions.assertEquals(new Generated("foo", Collections.emptyList()), objectMapper.readValue("{\"string\":\"foo\"}", Generated.class));
    Assertions.assertEquals(new Generated("foo", null), objectMapper.readValue("{\"string\":\"foo\",\"array\":null}", Generated.class));
    Assertions.assertEquals(new Generated("foo", Collections.emptyList()), objectMapper.readValue("{\"string\":\"foo\",\"array\":[]}", Generated.class));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-29
      • 1970-01-01
      • 2020-11-23
      • 1970-01-01
      • 1970-01-01
      • 2012-02-23
      • 2012-08-19
      • 2017-12-23
      相关资源
      最近更新 更多