我创建了一个 Jackson
Converter
,它按升序排列 Set
元素,以便生成的 JSON 数组按排序顺序输出,而不是任意迭代顺序发生由给定的 Set
实现使用。按照 StdConverter
的建议,它扩展了 Jackson 中的标准 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 找出元素类型?
答案 0 :(得分:2)
OrderedSetConverter
不知道在反序列化过程中必须实例化哪种类型。我们需要指示反序列化过程使用哪种类型。如果要保持转换器通用,则需要在创建转换器时提供此信息。要扩展默认行为,您需要实现自定义 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);
}
}
答案 1 :(得分:1)
Another answer 指出自定义 Jackson HandlerInstantiator
可用于在对象构造时在 Converter
内设置类型变量。该答案需要使用 @JsonDeserialize.contentAs
手动指定集合类型。在尝试了这种方法之后,我找到了一种在 HandlerInstantiator
中自动推导类型变量的方法:
HandlerInstantiator
public class OrderedSetConverterHandlerInstantiator extends HandlerInstantiator {
@Override
public Converter<?, ?> converterInstance(
MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
if (OrderedSetJacksonConverter.class.equals(implClass)) {
return new OrderedSetJacksonConverter<>(
getSetContentType(config, annotated));
} else {
return null;
}
}
private JavaType getSetContentType(MapperConfig<?> config, Annotated annotated) {
Type setType = getPropertyType(config, annotated);
return TypeFactory.defaultInstance().constructType(setType).getContentType();
}
private Type getPropertyType(MapperConfig<?> config, Annotated annotated) {
// TODO: Enhance to handle other cases, such as annotated constructor parameter
BeanPropertyDefinition beanPropertyDefinition =
SimpleBeanPropertyDefinition.construct(config,
(AnnotatedMember) annotated);
AnnotatedMember setter = beanPropertyDefinition.getSetter();
AnnotatedMember getter = beanPropertyDefinition.getGetter();
AnnotatedMember field = beanPropertyDefinition.getField();
Type setType;
if (setter != null) {
setType = ((Method) setter.getMember()).getGenericParameterTypes()[0];
} else if (getter != null) {
setType = ((Method) getter.getMember()).getGenericReturnType();
} else if (field != null) {
setType = ((Field) field.getMember()).getGenericType();
} else {
throw new UnsupportedOperationException(
"Annotated type not yet supported: " + annotated.getClass());
}
return setType;
}
// Implement other required methods such as serializerInstance to return null
}
Converter
public class OrderedSetConverter<T extends Comparable<? super T>>
extends StdConverter<Set<T>, Set<T>> {
private final JavaType contentType;
public OrderedSetJacksonConverter(JavaType contentType) {
this.contentType = contentType;
}
@Override
public Set<T> convert(Set<T> value) {
return value == null ? null : value.stream()
.sorted(Comparator.nullsLast(Comparator.naturalOrder()))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
@Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructCollectionType(Set.class, contentType);
}
@Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructCollectionType(Set.class, contentType);
}
}
ObjectMapper
配置
JsonMapper jsonMapper = JsonMapper.builder()
.handlerInstantiator(new OrderedSetConverterHandlerInstantiator())
该解决方案的一个笨拙之处是确定属性类型的方式,因为它手动迭代所有可能的带注释的元素类型,Annotated
值可能是 (getter/setter/field) 以确定元素类型。希望有一种更简洁、更惯用的方法,但如果有的话,我还没有找到。
Spring Web 附带了自己的 HandlerInstantiator
,SpringHandlerInstantiator
,它会自动应用于由 Jackson2ObjectMapperBuilder
创建的 ObjectMapper
。此处理程序将类路径中的任何相关 bean 用于 HandlerInstantiator
提供的类型。
为了在仍然使用自定义类型的同时保留这种行为,可以编写一个 HandlerInstantiator
类,在适当的时候使用自定义转换器,否则回退到默认的 SpringHandlerInstantiator
。一种实现方法如下:
public class DelegatingHandlerInstantiator extends SpringHandlerInstantiator {
private final OrderedSetConverterHandlerInstantiator handler =
new OrderedSetConverterHandlerInstantiator();
public DelegatingHandlerInstantiator(AutowireCapableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
public Converter<?, ?> converterInstance(
MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
Converter<?, ?> converter =
handler.converterInstance(config, annotated, implClass);
return Objects.requireNonNullElseGet(converter,
() -> super.converterInstance(config, annotated, implClass));
}
}
应用程序使用的 Jackson2ObjectMapperBuilder
然后应配置为使用此处理程序。如果应用程序是 Spring Boot 应用程序,则可以创建一个 Jackson2ObjectMapperBuilderCustomizer
bean 来设置 HandlerInstantiator
bean 中的 Jackson2ObjectMapperBuilder
。
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer(
AutowireCapableBeanFactory beanFactory) {
return b -> b.handlerInstantiator(
new DelegatingHandlerInstantiator(beanFactory));
}
}