如何在杰克逊中将 JSON 文档反序列化为预处理的 bean?



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

public class Wrapper {
public String string;
public List<String> array;
}

没有为stringarray设置默认值。 在某些情况下(主要是由于沉重的遗留原因(,如果输入 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会导致奇怪的异常 + 不确定它是否可以更新现有对象(。

我的杰克逊数据绑定版本是 2.9.10。

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

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

最新更新