I've created a Jackson Converter which orders Set elements in ascending sorted order, so that the resulting JSON array is output in sorted order, rather than whatever arbitrary iteration order happens to be used by the given Set implementation. It extended the standard StdConverter class in Jackson, as recommended by the Converter documentation.
NOTE: implementors are strongly encouraged to extend
StdConverterinstead of directly implementingConverter, since that can help with default implementation of typically boiler-plate code.
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;
}
This works well when the Converter is for a specific type of elements to convert (DayOfWeek in this example). However, there is nothing in this implementation that is specific to a particular type; it would work equally well for any Comparable type. Therefore, I'd prefer to have a generic implementation of this converter that can be used for any Set of comparables.
I've attempted to implement this by using a straightforward usage of generics:
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));
}
}
This does not work as I would hope, since Jackson tries (and fails) to convert the set's element type to Comparable, rather than the actual type (e.g. 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)
About the best I've been able to come up with is to use this as an abstract superclass, and have a Converter of each specific subtype for each type that's actually used:
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;
}
How can I write a converter for a generic collection type and have Jackson figure out the element type?