java - 如何编写 Jackson 转换器来处理通用集合类型?
问题描述
我创建了按升序排序元素的方法,以便生成的 JSON 数组按排序顺序输出,而不是给定实现碰巧使用的任意迭代顺序。按照文档的建议,它扩展了Jackson 中的标准类。Jackson
Converter
Set
Set
StdConverter
Converter
注意:强烈鼓励实现者扩展
StdConverter
而不是直接实现Converter
,因为这有助于默认实现典型的样板代码。
public class OrderedSetConverter
extends StdConverter<Set<DayOfWeek>, Set<DayOfWeek>> {
@Override
public Set<DayOfWeek> convert(Set<DayOfWeek> value) {
return value == null ? null : value.stream()
.sorted(Comparator.nullsLast(Comparator.naturalOrder()))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
}
public class MyType {
@JsonSerialize(converter = OrderedSetConverter.class)
private Set<DayOfWeek> myValues;
}
这适用Converter
于要转换的特定类型的元素(DayOfWeek
在本例中)。但是,此实现中没有特定于特定类型的内容。它适用于任何Comparable
类型。因此,我更喜欢这个转换器的通用实现,它可以用于任何Set
可比较的对象。
我试图通过直接使用泛型来实现这一点:
public class OrderedSetConverter<E extends Comparable<? super E>>
extends StdConverter<Set<E>, Set<E>> {
@Override
public Set<E> convert(Set<E> value) {
return value == null ? null : value.stream()
.sorted(Comparator.nullsLast(Comparator.naturalOrder()))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
}
这不像我希望的那样工作,因为杰克逊尝试(并且失败)将集合的元素类型转换为Comparable
,而不是实际类型(例如DayOfWeek
)。
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.lang.Comparable` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"daysOfWeek":["MONDAY","TUESDAY","WEDNESDAY","THURSDAY","FRIDAY","SATURDAY","SUNDAY"]}"; line: 1, column: 16] (through reference chain: com.example.MyType["daysOfWeek"]->java.util.HashSet[0])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1209)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:347)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
at com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer.deserialize(StdDelegatingDeserializer.java:175)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516)
关于我能想到的最好的方法是将其用作抽象超类,并Converter
为实际使用的每种类型拥有一个特定的子类型:
public abstract class OrderedSetConverter<E extends Comparable<? super E>>
extends StdConverter<Set<E>, Set<E>> {
@Override
public Set<E> convert(Set<E> value) {
return value == null ? null : value.stream()
.sorted(Comparator.nullsLast(Comparator.naturalOrder()))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
}
public class DayOfWeekSetConverter extends OrderedSetConverter<DayOfWeek> { }
public class MyType {
@JsonSerialize(converter = DayOfWeekSetConverter.class)
private Set<DayOfWeek> myValues;
}
如何为通用集合类型编写转换器并让 Jackson 找出元素类型?
解决方案
OrderedSetConverter
不知道在反序列化过程中必须实例化哪种类型。我们需要指示反序列化过程使用哪种类型。如果要保持转换器通用,则需要在创建转换器时提供此信息。要扩展默认行为,您需要实现 custom com.fasterxml.jackson.databind.cfg.HandlerInstantiator
:
class ConverterHandlerInstantiator extends HandlerInstantiator {
@Override
public Converter<?, ?> converterInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
if (config instanceof DeserializationConfig) {
JsonDeserialize jsonDeserialize = annotated.getAnnotation(JsonDeserialize.class);
Class contentAs = jsonDeserialize.contentAs();
if (contentAs != Void.class) {
return new OrderedSetConverter<>(contentAs);
}
}
return super.converterInstance(config, annotated, implClass);
}
@Override
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated, Class<?> deserClass) {
return null;
}
@Override
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, Class<?> keyDeserClass) {
return null;
}
@Override
public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated, Class<?> serClass) {
return null;
}
@Override
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated, Class<?> builderClass) {
return null;
}
@Override
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated, Class<?> resolverClass) {
return null;
}
}
正如您注意到上面的代码使用注释中的contentAs
属性。JsonDeserialize
我们需要在您的课程中指定它:
class MyType {
@JsonSerialize(converter = OrderedSetConverter.class)
@JsonDeserialize(converter = OrderedSetConverter.class, contentAs = DayOfWeek.class)
private Set<DayOfWeek> myValues;
}
当我们序列化对象时不需要这个值,所以我们可以跳过它。现在,我们需要注册我们的自定义实例化器:
ObjectMapper mapper = new ObjectMapper();
mapper.setHandlerInstantiator(new ConverterHandlerInstantiator());
最后,我们的通用转换器:
class OrderedSetConverter<E extends Comparable<? super E>> extends StdConverter<Set<E>, Set<E>> {
private final Class<E> contentClass;
OrderedSetConverter() {
this(null);
}
public OrderedSetConverter(Class<E> contentClass) {
this.contentClass = contentClass;
}
@Override
public Set<E> convert(Set<E> value) {
return value == null ? null : value.stream()
.sorted(Comparator.nullsLast(Comparator.naturalOrder()))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
@Override
public JavaType getInputType(TypeFactory typeFactory) {
if (contentClass == null) {
return super.getInputType(typeFactory);
}
return typeFactory.constructCollectionType(Set.class, contentClass);
}
}
推荐阅读
- ruby-on-rails - 测试在 ruby 中调用 3rd 方 api 的客户端类的内容是什么?
- amazon-cloudformation - AppSync Cloudformation 模板生成器
- javascript - 使用 React Bootstrap 自定义 Card.Header
- react-native - 如何使用 useState 挂钩更新对象
- c# - 我可以使用 Environment.Username 和 MYSQL 来验证登录吗?
- python - How to check if a PNG has a certain RGB color using Python?
- python-3.x - 如何在 html 表中抓取?
- airflow - gcloud composer command fails "executable file not found in $PATH"
- ios - 能够在 IOS 中使用的转换器条带
- javascript - How can I call a variable outside the widget