Jackson: вернуть типизированный объект вместо LinkedHashMap
Быстрый ответ
Для быстрой десериализации JSON в обобщённый Java-тип с помощью библиотеки Jackson необходимо использовать TypeReference
:
ObjectMapper mapper = new ObjectMapper();
String json = "[{\"name\":\"example\"}]";
TypeReference<List<MyClass<String>>> typeRef = new TypeReference<>() {};
List<MyClass<String>> list = mapper.readValue(json, typeRef);
Анонимный подкласс TypeReference
быстро справляется с вопросами типизации, обеспечивая правильную десериализацию для метода readValue
.
Работа с десериализацией пользовательских обобщённых типов
Рассмотрим проблему десериализации JSON в пользовательские обобщённые типы коллекций в Jackson. Преодолеваем проблему стирания типов в Java, используя JavaType
:
//Не забывайте: возможности JavaType не ограничены!
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, MyClass.class);
List<MyClass> myClasses = mapper.readValue(json, type);
Такой подход с применением JavaType
позволяет точно определить обобщённый тип коллекции для Джексона и является более эффективным, нежели использование Class<?>
.
Решение задачи со сложными вложенными обобщениями
Для работы со сложной структурой JSON, содержащей вложенные обобщения типа List<Map<String, List<MyClass>>>
, воспользуйтесь функциональностью JavaType
библиотеки Jackson:
// Любите JavaType? Добавим JavaType к вашему JavaType!
JavaType innerListType = mapper.getTypeFactory().constructCollectionType(List.class, MyClass.class);
JavaType mapType = mapper.getTypeFactory().constructMapType(Map.class, String.class, innerListType);
JavaType outerListType = mapper.getTypeFactory().constructCollectionType(List.class, mapType);
List<Map<String, List<MyClass>>> listOfMaps = mapper.readValue(json, outerListType);
Построение иерархии JavaType
позволяет наглядно отражать все уровни обобщённых типов ваших JSON-данных.
Обход скрытия типов с помощью границ
Исчезновение типов в Java может доставить массу хлопот. Однако определение границ типа позволяет избежать превращения T
в Object
, обеспечивая тем самым чёткую типизацию:
public class GenericHolder<T extends MyClass> {
public T value;
public static <T extends MyClass> GenericHolder<T> fromJson(String json, Class<T> clazz) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametricType(GenericHolder.class, clazz);
return mapper.readValue(json, type);
}
}
Указывая Джексону, что T
является подклассом MyClass
, мы сохраняем обобщённость кода и гарантируем правильную десериализацию для нужного класса.
Визуализация
Ссылка на Обобщённый Тип🔑: new TypeReference<List<String>>() {}
Без Ключа (TypeRef): `List list = objectMapper.readValue(jsonInput, List.class);`
🔓: [📦📦📦] (🤔 Что там внутри?)
С Ключом (TypeRef): `List<String> list = objectMapper.readValue(jsonInput, new TypeReference<List<String>>() {});`
🔑🔓: [📜📜📜] (✅ Теперь ясно, что наш список содержит строки!)
Владение TypeReference
дает нам ключ к разгадке точной информации о обобщённом типе, что способствует правильной десериализации данных Джексоном.
Работа с пользовательскими классами и аннотациями
Пользовательские классы могут решать сложные задачи маршалинга. Правильное применение аннотаций поможет Джексону корректно интерпретировать свойства классов:
//Добро пожаловать на станцию аннотаций Джексон!
public class Station {
@JsonProperty("name")
String stationName;
}
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector());
String jsonInput = "[{\"name\":\"Central Station\"}]";
List<Station> stations = mapper.readValue(jsonInput, new TypeReference<List<Station>>() {});
Декодирование JSON и проверка корректности вывода
JSON-данные могут представлять собой не только String
, но и InputStream
, который нужно преобразовать:
//Это — преобразование данных из InputStream в String, а не взлом.
String dataJson = new String(Files.readAllBytes(Paths.get("data.json")), StandardCharsets.UTF_8);
List<Station> stations = mapper.readValue(dataJson, new TypeReference<List<Station>>() {});
stations.forEach(station -> System.out.println(station.getStationName()));
Ожидаемый результат десериализации поможет убедиться, что все прошло так гладко, как вы это планировали.
Полезные материалы
- TypeReference (Jackson-core 2.12.0 API) — здесь вы найдёте подробную информацию о
TypeReference
. - Java – Constructor annotated with @JsonCreator, why @JsonProperty on arguments? – Stack Overflow — дискуссия на Stack Overflow о важности использования
@JsonCreator
и@JsonProperty
. - Generic Types (The Java™ Tutorials > Learning the Java Language > Generics (Updated)) — углубите знания о обобщённых типах Java с помощью официальных руководств Oracle.
- Jackson – Быстрая навигация — полезное руководство по использованию Jackson для работы с JSON в Java.