Jackson ObjectMapper: Can java.util.Set be serialized to JSON array in ascending sorted order?

Viewed 9147

I have a Java java.util.Set of Comparable types which I would like serialized to a JSON array in ascended sorted order.

There's an object mapper feature to serialize maps sorted by map keys, but nothing is said about java sets. If I store items in a java.util.Set in my POJO, will the Jackson ObjectMapper serialize them to the JSON array in any kind of order?

If not, I'm thinking of writing a custom serializer for the set for this purpose - is there a better way that someone can suggest?


Ultimately, the goal is to deserialize an array of unique integers into some sort of container in a "request" pojo. Then process the request and pass that input field into the corresponding output field (unchanged) in a "response" object.

I'd like to store the data in the POJOs as java.util.Set<Integer> just to keep all the users happy (if you try to store it as as subtype of Set then consumers of the POJO's interface may be upset that they can't just get a Set but have to expect a HashSet or a TreeSet).

Will this work?

  1. JSON array [5,4,3,2,1] is deserialized into Set<Integer> BUT, I use @JsonDeserialize(as=TreeSet.class) on the setter method. So the runtime type of the object is now a TreeSet (even though it's stored in the POJO as a Set).
  2. The Set is passed around through the server and ultimately ends being stored in the response POJO (runtime type is still TreeSet, even though the POJO field type is Set).
  3. ObjectMapper serializes a TreeSet to JSON array [1,2,3,4,5] (because TreeSet is sorted naturally).

Item 3 above is the one I'm wondering about - will ObjectMapper use the runtime type of the POJO's field, or the declared type?


How can I ensure my Set is converted to a JSON array in sorted order?

2 Answers

A JSON Array is an ordered collection, exactly like a Java array is:

An array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).

However, ordered does not mean sorted. It just means that it retains the order of the given values:

  • If the Set is sorted (e.g. TreeSet), then the JSON array is sorted, according to the sort order of the Set:

    TreeSet: The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used.

  • If the Set is unordered (e.g. HashSet), then the JSON array is in the arbitrary order returned by the Set.

    HashSet: It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.

This is unlike the fields of a JSON Object:

An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are separated by , (comma).

A Jackson Converter can be created to order the Set, converting it to a LinkedHashSet with elements in the desired sort order.

public class OrderedSetConverter 
        extends StdConverter<Set<Integer>, Set< Integer >> {
    @Override
    public Set<DayOfWeek> convert(Set<Integer> 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<Integer> myValues;
}

When applied to a property using JsonSerialize, the resulting JSON array will be in sorted order. Additionally, this could be applied to a property using JsonDeserialize, ensuring that the object resulting from reading JSON will have a LinkedHashSet with a sorted iteration order.


I used this approach in my project rather than using a TreeSet for a few reasons. First, I wanted to accept null values in the set (mainly so that I could have use my standard null value handling deal with them with an appropriate API error message, rather than have a NullPointerException occur when creating the TreeSet. Secondly, TreeSet changes the semantics of the set from the standard set contract, using the Comparable object's compareTo method to determine whether two objects are equal, rather than Objevt.equals(). Finally, I wanted my clients of the Java API to be able to use any valid Set type that made sense for their applications, rather than being restricted to TreeSet.

Related