Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get Jackson to serialize an unsorted set in sorted order?

I have a Java object containing an unsorted set of comparable objects. Is there any way to make Jackson sort this set before serializing it into a JSON list?

I want this behavior so that objects that are equal when represented in Java deterministically produce the same JSON.

Is there some setting in Jackson that can do this? Or is my best bet to write a custom serializer?

Can a single custom serializer work for all Set<T> where T implements Comparable?

This question is not a duplicate of jackson to sort the response by using the field name alone, which asks how to sort keys in a Map. This question is about sorting elements in a Set.

like image 313
TheN33k Avatar asked Sep 06 '19 03:09

TheN33k


People also ask

Does Jackson use serializable?

Note that Jackson does not use java. io. Serializable for anything: there is no real value for adding that. It gets ignored.

Does Jackson serialize getters?

Jackson by default uses the getters for serializing and setters for deserializing.

What is Jackson object serialization?

Jackson is a solid and mature JSON serialization/deserialization library for Java. The ObjectMapper API provides a straightforward way to parse and generate JSON response objects with a lot of flexibility. This article discussed the main features that make the library so popular.

How does Jackson serialize null?

Serialize Null Fields Fields/PropertiesWith its default settings, Jackson serializes null-valued public fields. In other words, resulting JSON will include null fields. Here, the name field which is null is in the resulting JSON string.


1 Answers

Even if you use HashSet implementation of Set interface it produces the same result when two sets contain the same objects. So, you do not need to sort set to produce the same JSON for two different but equal (contain same objects) sets.

But if you want to force Jackson to sort collection before serialisation you need to implement custom serialiser.

We can only register serialiser for Set and it will be used for sets which contain Comparable objects and for sets which do not so it must be checked in custom implementation.

Example implementation could look like below:

class SortedSetJsonSerializer extends JsonSerializer<Set> {

    @Override
    public void serialize(Set set, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (set == null) {
            gen.writeNull();
            return;
        }

        gen.writeStartArray();
        if (!set.isEmpty()) {
            // create sorted set only if it itself is not already SortedSet
            if (!SortedSet.class.isAssignableFrom(set.getClass())) {
                Object item = set.iterator().next();
                if (Comparable.class.isAssignableFrom(item.getClass())) {
                    // and only if items are Comparable
                    set = new TreeSet(set);
                }
            }
            for (Object item : set) {
                gen.writeObject(item);
            }
        }
        gen.writeEndArray();
    }
} 

And example usage:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.IntStream;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        Set<Integer> ints = newSet();

        SimpleModule module = new SimpleModule();
        module.addSerializer(Set.class, new SortedSetJsonSerializer());
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(module);

        System.out.println(mapper.writeValueAsString(new MyModel(ints)));
    }

    private static Set<Integer> newSet() {
        Set<Integer> ints = new HashSet<>();
        IntStream.range(10, 20).forEach(ints::add);

        return ints;
    }
}

class MyModel {
    private Set<Integer> integers;

    public MyModel(Set<Integer> integers) {
        this.integers = integers;
    }

    public Set<Integer> getIntegers() {
        return integers;
    }

    public void setIntegers(Set<Integer> integers) {
        this.integers = integers;
    }
}

prints:

{"integers":[10,11,12,13,14,15,16,17,18,19]}
like image 172
Michał Ziober Avatar answered Oct 15 '22 02:10

Michał Ziober