Когда мы работаем с сериализацией и десериализацией JSON с использованием библиотеки Jackson, часто возникает необходимость преобразования JSON в сложные обобщённые типы, такие как List или Map. Для этого Jackson предоставляет класс TypeReference, который решает проблему десериализации обобщённых типов.
Проблема десериализации generic типов
Предположим, у нас есть JSON-строка, которая представляет список объектов типа ApplicationDto. Если мы попытаемся десериализовать её с помощью стандартного метода ObjectMapper.readValue(), как это делается для обычных классов, мы столкнёмся с проблемой, так как тип List<ApplicationDto> является обобщённым, и Jackson не может корректно определить тип элементов списка без дополнительной информации.
Пример кода, который приведёт к ошибке:
ObjectMapper objectMapper = new ObjectMapper();
List<ApplicationDto> result = objectMapper.readValue(message.data, List<ApplicationDto>.class);
Ошибка:
Only classes are allowed on the left hand side of a class literal
Это происходит из-за того, что List<ApplicationDto> — это параметризованный тип, а JVM на этапе компиляции теряет информацию о типах элементов списка из-за явления, называемого Type Erasure (стирание типов). Для решения этой проблемы используется класс TypeReference.
Использование TypeReference в Java
Jackson предоставляет класс TypeReference, который позволяет указать обобщённый тип для десериализации. Пример использования в Java:
ObjectMapper objectMapper = new ObjectMapper();
List<ApplicationDto> result = objectMapper.readValue(
message.data, new TypeReference<List<ApplicationDto>>() {});
Здесь класс TypeReference помогает Jackson получить информацию о конкретном типе данных (в нашем случае — это List<ApplicationDto>), которую оно использует для корректной десериализации.
Использование TypeReference в Kotlin
Если вы попытаетесь использовать TypeReference в Kotlin аналогичным образом, вы можете столкнуться с ошибкой:
Cannot access '<init>': it is protected in 'TypeReference'
Это связано с тем, что в Kotlin конструктор класса TypeReference защищён и не может быть вызван напрямую, как в Java. Однако Kotlin предоставляет удобный способ создания анонимных объектов, который позволяет обойти это ограничение.
Для правильного использования TypeReference в Kotlin необходимо создать анонимный объект, унаследованный от TypeReference, используя следующую конструкцию:
val objectMapper = ObjectMapper()
val result: List<ApplicationDto> = objectMapper.readValue(
message.data, object : TypeReference<List<ApplicationDto>>() {})
В этом коде выражение object : TypeReference<List<ApplicationDto>>() {} создаёт анонимный объект, который унаследован от TypeReference, позволяя таким образом корректно указать тип для десериализации.