Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deserialize generic List<T> with Jackson?

I've been using Jackson to serialize/deserialize objects for years and have always found it needlessly complicated to use TypeReference<T> to deserialize List etc. I created a simple helper function:

public static <T> TypeReference<List<T>> list() {
    return new TypeReference<List<T>>(){}
}

With intended use:

List<Foo> foos = objectMapper.readValue(json, list());

And it works! Kind of. When inspecting through the debugger, rather than a list of Foo, it is rather a list of LinkedHashMap. I understand that ObjectMapper deserializes into LinkedHashMap for type Object and I read the explanation for that here:

Jackson and generic type reference

However, why is it able to assign List<LinkedHasMap> to a List<Foo>? At the very least shouldn't that be some sort of ClassCastException?

Also, is there anyway to do this with Java's type system?

NOTE: the following method declaration has the same issue, which makes sense because the additional argument is not needed for T to be determined:

public static <T> TypeReference<List<T>> listOf(Class<T> ignored) {
    return new TypeReference<List<T>>(){}
}
like image 527
JDrost1818 Avatar asked Apr 11 '20 01:04

JDrost1818


People also ask

What does Jackson ObjectMapper do?

The Jackson ObjectMapper can parse JSON from a string, stream or file, and create a Java object or object graph representing the parsed JSON. Parsing JSON into Java objects is also referred to as to deserialize Java objects from JSON. The Jackson ObjectMapper can also create JSON from Java objects.

What is Jackson's TypeReference?

Class TypeReference<T>This generic abstract class is used for obtaining full generics type information by sub-classing; it must be converted to ResolvedType implementation (implemented by JavaType from "databind" bundle) to be used.

How do you tell Jackson to ignore a field during serialization?

If there are fields in Java objects that do not wish to be serialized, we can use the @JsonIgnore annotation in the Jackson library. The @JsonIgnore can be used at the field level, for ignoring fields during the serialization and deserialization.


1 Answers

It works like this because of type erasure in Java. Please, read about it before you start reading next part of this answer:

  • Type Erasure
  • Type Erasure in Java Explained
  • Java generics type erasure: when and what happens?

As you probably know right now, after reading above articles, your method after compilation looks like this:

static <T> TypeReference<List> listOf(Class<T> ignored) {
    return new TypeReference<List>(){};
}

Jackson will try to find out the most appropriate type for it which will be java.util.LinkedHashMap for a JSON Object. To create irrefutable type you need to use com.fasterxml.jackson.databind.type.TypeFactory class. See below example:

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;

import java.io.File;
import java.util.List;

public class JsonTypeApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        ObjectMapper mapper = new ObjectMapper();

        System.out.println("Try with 'TypeFactory'");
        List<Id> ids = mapper.readValue(jsonFile, CollectionsTypeFactory.listOf(Id.class));
        System.out.println(ids);
        Id id1 = ids.get(0);
        System.out.println(id1);

        System.out.println("Try with 'TypeReference<List<T>>'");
        List<Id> maps = mapper.readValue(jsonFile, CollectionsTypeFactory.erasedListOf(Id.class));
        System.out.println(maps);
        Id maps1 = maps.get(0);
        System.out.println(maps1);
    }
}

class CollectionsTypeFactory {
    static JavaType listOf(Class clazz) {
        return TypeFactory.defaultInstance().constructCollectionType(List.class, clazz);
    }

    static <T> TypeReference<List> erasedListOf(Class<T> ignored) {
        return new TypeReference<List>(){};
    }
}

class Id {
    private int id;

    // getters, setters, toString
}

Above example, for below JSON payload:

[
  {
    "id": 1
  },
  {
    "id": 22
  },
  {
    "id": 333
  }
]

prints:

Try with 'TypeFactory'
[{1}, {22}, {333}]
{1}
Try with 'TypeReference<List<T>>'
[{id=1}, {id=22}, {id=333}]
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Id
    at com.example.JsonTypeApp.main(JsonTypeApp.java:27)

See also:

  • How to use Jackson's TypeReference with generics?
  • Jackson create JavaType from Class
like image 133
Michał Ziober Avatar answered Oct 25 '22 22:10

Michał Ziober