Как десериализовать generi c List <T>с Джексоном? - PullRequest
1 голос
/ 11 апреля 2020

Я использовал Джексона для сериализации / десериализации объектов в течение многих лет и всегда находил ненужным усложнять использование TypeReference<T> для десериализации List и c. Я создал простую вспомогательную функцию:

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

Для использования по назначению:

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

И это работает! Вроде. При проверке через отладчик, а не список Foo, это скорее список LinkedHashMap. Я понимаю, что ObjectMapper десериализуется в LinkedHashMap для типа Object, и я читаю объяснение этому здесь:

Джексон и Дженис c ссылка типа

Однако почему он может назначить List<LinkedHasMap> на List<Foo>? По крайней мере, это не должно быть чем-то вроде ClassCastException?

Кроме того, есть ли в любом случае сделать это с системой типов Java?

ПРИМЕЧАНИЕ: следующее объявление метода имеет ту же проблему, что имеет смысл, поскольку дополнительный аргумент не требуется для T будет определено:

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

1 Ответ

1 голос
/ 11 апреля 2020

Это работает так из-за стирания типа в Java. Пожалуйста, прочитайте об этом, прежде чем вы начнете читать следующую часть этого ответа:

Как вы, вероятно, знаете прямо сейчас, после прочтения вышеупомянутых статей, ваш метод после компиляции выглядит так:

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

Jackson попытается найти наиболее подходящий для него тип, который будет java.util.LinkedHashMap для JSON Object. Для создания неопровержимого type вам нужно использовать com.fasterxml.jackson.databind.type.TypeFactory класс. Ниже приведен пример:

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
}

Выше приведен пример для ниже JSON полезная нагрузка:

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

отпечатков:

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)

См. Также:

...