Some notes on Jackson’s ObjectMapper, when using it without any custom POJO classes, to deserialize an arbitrary piece of JSON to a Java Map.

I have some arbitrary JSON:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "total_in_month": 95,
  "status": "valid",
  "data": [
    {
      "total_on_date": 47,
      "date": "01-01-2020"
    },
    {
      "total_on_date": 48,
      "date": "08-01-2020"
    }
  ]
}

I can use these classes to deserialize this JSON to an object:

I can use ObjectMapper on its own using a raw, untyped java.util.Map:

1
2
ObjectMapper objectMapper = new ObjectMapper();
Map map = objectMapper.readValue(getJsonString(), Map.class);

This gives me all the Java data types I expect:

But it uses a raw Map. This is not the right approach - not type-safe and not self-documenting.

Or, I can use Jackson’s TypeReference with my ObjectMapper:

1
2
3
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper
        .readValue(getJsonString(), new TypeReference<Map<String, Object>>() {});

This new TypeReference<Map<String, Object>>() {} means the map is no longer untyped. It creates the exact same end result as the first example.

This is still not ideal, because you will be dealing with unknown types at runtime, for those Object values. But it avoids the need for customized POJO classes, as well as avoiding the raw Map.

Another way to use TypeReference is as follows:

1
2
TypeReference<Map<String, Object>> ref = new TypeReference<>() {};
Map<String, Object> map = objectMapper.readValue(s, ref);

Note the use of {} in the syntax for declaring the new TypeReference. TypeReference is an abstract class. The {} provides an empty implementation via an anonymous class, without which you would get a compile-time error:

TypeReference is abstract; cannot be instantiated

Digression…

You may sometimes (rarely?) see {{}} syntax - this is Java’s double brace initialization. Consider this code:

1
2
3
4
List<Integer> foo = new ArrayList<Integer>() {{
        add(1);
        add(2);
}};

This creates a list containing two values.

The outer braces create an anonymous class derived from the specified class (ArrayList in this case). The inner braces define an initialiser block within the inner class.

So, when we have single {} braces like we do in the TypeReference usage, that means there is no initializer block in the inner class. In fact there is nothing at all in the anonymous class - it is completely empty.

I think this double-brace syntax is relatively rare. I have never seen it outside of tutorials and Stack Overflow questions. And blog posts discouraging its use.

…end digression.

You cannot execute this code:

1
TypeReference ref = new TypeReference(){};

It will throw a runtime error:

IllegalArgumentException TypeReference constructed without actual type information

You can see how this is caught in the source code here:

1
2
3
4
5
6
7
8
protected TypeReference()
{
    Type superClass = getClass().getGenericSuperclass();
    if (superClass instanceof Class<?>) { // sanity check, should never happen
        throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
    }
    ...
}

Final note: The TypeReference class implements Comparable. The javadoc states that this is because doing so forces a compile-time check to ensure you have provided a type when instantiating a new TypeReference object:

The class uses a…

…bogus implementation of Comparable (any such generic interface would do, as long as it forces a method with generic type to be implemented), to ensure that a Type argument is indeed given.

and:

compareTo(): The only reason we define this method (and require implementation of Comparable) is to prevent constructing a reference without type information.

In reality, however, none of this appears to be the case. As we have already seen, there is no compile time error for the following code:

1
2
// bad idea (using a raw type) - just for illustration:
TypeReference typeReference = new TypeReference() {};

The javadoc does not appear to be correct, here. Maybe it was in an earlier version of Java.