import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

@SuppressWarnings("unchecked")
public class Main {
    public static void main(String[] args) throws JsonProcessingException {
        //Serializing a TreeMap with String keys and String values
        TreeMap<String, String> mappings = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        mappings.put("Test3", "3");
        mappings.put("test1", "1");
        mappings.put("Test2", "2");

        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mappings);
        System.out.println("mappings: \n" + json);

        // Deserializing to a generic type without TypeReference (wrong approach).
        //
        // First of all, a small side note on deserializing with an interface. When you don't use a specific implementation
        // but an interface when deserializing Maps, the default implementation used by Jackson is a HashMap. This is because
        // an interface is not a concrete class, while Jackson needs an actual Map implementation to deserialize the Json
        // values somewhere. This is what was happening to you, judging from the initial problem described in the comments:
        //
        // > "I originally had this deserializing to a field which was Map<String, String> but when I checked if it was a
        // > TreeMap using mappings instanceof TreeMap it returned false. It returned true when I did mappings instanceof HashMap."
        //
        // Now, back to the TypeReference approach. In this case, even without using a TypeReference instance, the
        // deserialization happens successfully because json values are read as String, which is coincidentally the same
        // type of your Map's values. However, in general, when deserializing generic types you should subclass the actual
        // type representing your data. This is because the argument type tells Jackson how to unmarshall those values.
        Map<String, String> mappingsAsHashMap = objectMapper.readValue(json, Map.class);
        System.out.println(mappingsAsHashMap instanceof TreeMap<String, String>);   //Prints false because, by default, it was deserialized with a HashMap

        Map<String, String> mappinggsAsTreeMap = objectMapper.readValue(json, TreeMap.class);
        System.out.println(mappinggsAsTreeMap instanceof TreeMap<String, String>);  //Prints true because you specified the actual implementation

        //Works because the values have been read as a String
        Map<String, String> mappingsWithStringValues = objectMapper.readValue(json, TreeMap.class);
        String s = mappingsWithStringValues.get("test1");
        System.out.println("length of s :" + s.length());

        //Fails because tries to treat the values as Integer while the actual instances are String
        Map<String, Integer> mappingsWithIntValues = objectMapper.readValue(json, TreeMap.class);
        try {
            Integer i = mappingsWithIntValues.get("test1");
            System.out.println(i.intValue());
        } catch (ClassCastException ex){
            System.out.println("Attempting to read a String as an Integer");
        }


        //Serializing a TreeMap with String keys and a custom Bean
        TreeMap<String, MyBean> mappingsBean = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        mappingsBean.put("Test3", new MyBean(3, "MyBean3"));
        mappingsBean.put("test1", new MyBean(1, "MyBean1"));
        mappingsBean.put("Test2", new MyBean(2, "MyBean2"));

        String jsonBean = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mappingsBean);
        System.out.println("\n" + "mappingsBean:\n" + jsonBean);

        // Deserializing the mappingsBean json without a TypeReference (wrong approach).
        //
        // Like so, Jackson doesn't know to which data type those inner values ({"id" : 2,"name" : "MyBean2"}) correspond to,
        // therefore that sequence of key-value pairs is read as a LinkedHashMap, and even worse, it is added as such within
        // your Map despite being a TreeMap<String, MyBean>! In fact, if you attempt to read or perform any operation on
        // its values, you will raise a ClassCastException as the type expected for your values is MyBean and not LinkedHashMap.
        mappingsBean = objectMapper.readValue(jsonBean, TreeMap.class);
        try {
            MyBean myBean = mappingsBean.get("test1");
        } catch (ClassCastException ex) {
            System.out.println("Attempting to read a LinkedHashMap as a MyBean");
        }

        try {
            System.out.println(mappingsBean.get("test1").getId());
        } catch (ClassCastException ex) {
            System.out.println("Attempting to perform a MyBean's operation on a LinkedHashMap");
        }

        // Deserializing the mappingsBean json with a TypeReference (right approach).
        //
        // With this approach, Jackson knows via the argument type of the TypeReference, that those inner values
        // ({"id" : 2,"name" : "MyBean2"}) correspond to a MyBean that needs to be reconstructed.
        mappingsBean = objectMapper.readValue(jsonBean, new TypeReference<TreeMap<String, MyBean>>() {
        });
        System.out.println("\n" + mappingsBean);


        //Proper deserialization of your original Map while maintaining the case insensitive order.
        //
        // If you attempt to simply read the initial json with a TypeReference that subclasses a TreeMap<String, String>
        // you'll find out that it won't maintain your custom ordering, since the default ordering is case-sensitive.
        // This is noticeable when looking at the print of mappingsBean, it follows the default case-sensitive ordering.
        // Therefore, if you want to read your json as a TreeMap and maintain your custom case insensitive ordering, you
        // need to define a temporary TreeMap where to read the json, and a result TreeMap initialized with the custom
        // comparator and all the key-value pairs from the temporary TreeMap.

        //Wrong ordering mappings
        System.out.println("\nWrong Ordering mappings:\n" + objectMapper.readValue(json, new TypeReference<TreeMap<String, String>>() {
        }));

        //Right ordering mappings
        TreeMap<String, String> mappingsTemp = objectMapper.readValue(json, new TypeReference<TreeMap<String, String>>() {
        });
        TreeMap<String, String> mappingsRes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        mappingsRes.putAll(mappingsTemp);
        System.out.println("\nRight Ordering mappings:\n" + mappingsRes);
    }

    static class MyBean {
        private int id;
        private String name;

        public MyBean(){}

        public MyBean(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            MyBean myBean = (MyBean) o;
            return id == myBean.id && Objects.equals(name, myBean.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(id, name);
        }

        @Override
        public String toString() {
            return "MyBean{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
} 
by