【发布时间】:2020-06-26 20:58:32
【问题描述】:
我正在尝试弄清楚如何将我在项目中使用的代码生成器和 Jackson 结合起来,以便将它们结合起来。 第三方 bean 代码生成器做了一些我想改进的事情。 比如下面的类
public class Wrapper {
public String string;
public List<String> array;
}
没有为string 和array 设置默认值。
在某些情况下(主要是由于 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。
【问题讨论】: