我已经创建了一个Jackson
Converter
,它以升序排列Set
元素,因此生成的JSON数组以排序顺序输出,而不是给定Set
实现所使用的任意迭代顺序。它扩展了Jackson中的标准StdConverter
类,正如Converter
documentation所推荐的那样。
注意:强烈建议实现者扩展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));
}
}
这并不像我希望的那样工作,因为Jackson试图(但失败了)将集合的元素类型转换为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找出元素类型?
3条答案
按热度按时间fcwjkofz1#
OrderedSetConverter
不知道在初始化过程中必须示例化哪种类型。我们需要指导石化过程使用哪种类型。如果你想保持转换器的通用性,你需要在创建转换器时提供这些信息。要扩展默认行为,您需要实现自定义com.fasterxml.jackson.databind.cfg.HandlerInstantiator
:正如你在上面注意到的,代码使用了来自
JsonDeserialize
注解的contentAs
属性。我们需要在你的类中指定它:当我们序列化对象时,这个值是不需要的,所以,我们可以跳过它。现在,我们需要注册我们的自定义instantiator:
最后,我们的通用转换器:
r3i60tvu2#
Another answer指出,可以使用自定义的Jackson
HandlerInstantiator
在对象构造时在Converter
中设置类型变量。这个答案需要使用@JsonDeserialize.contentAs
手动指定集合类型。在尝试了这种方法之后,我发现了一种在HandlerInstantiator
中自动推导类型变量的方法:HandlerInstantiator
Converter
ObjectMapper
配置解决方案的一个笨拙之处在于确定属性类型的方式,因为它手动迭代
Annotated
值可能是(getter/setter/field)的所有可能的注解元素类型来确定元素类型。希望有一个更干净,更习惯的方法,虽然如果有,我还没有找到它。在Spring framework中注册转换器
Spring Web附带了自己的
HandlerInstantiator
,SpringHandlerInstantiator
,它会自动应用到由Jackson2ObjectMapperBuilder
创建的ObjectMapper
。此处理程序使用HandlerInstantiator
提供的类型的类路径中的任何相关bean。为了在使用自定义类型的同时保持这种行为,可以编写一个
HandlerInstantiator
类,在适当的时候使用自定义转换器,否则回退到默认的SpringHandlerInstantiator
。实现这一点的一种方式如下:然后,应用程序使用的
Jackson2ObjectMapperBuilder
应该配置为使用此处理程序。如果应用程序是Spring Boot应用程序,则可以创建Jackson2ObjectMapperBuilderCustomizer
bean,以在Jackson2ObjectMapperBuilder
bean中设置HandlerInstantiator
。yqhsw0fo3#
一种解决方案是使用
JsonSerializer
,而不是Converter
。同样地,为了强制执行格式化顺序,可以使用
JsonDeserializer
。