Как использовать TypeToken с дженериками в Gson в Kotlin (решение ошибки com.google.gson.internal.LinkedTreeMap)
- >(){}.getType()`, но в Kotlin это не работает из-за erased generics.
Вот упрощённый пример:
```kotlin
data class User(val name: String, val age: Int)
fun
- inline: функция встраивается в место вызова, поэтому компилятор "знает" конкретный тип T.
- reified: позволяет использовать T внутри функции, хотя обычно дженерики стираются.
- object : TypeToken<T>() {}: создаёт анонимный класс, который наследуется от TypeToken. Благодаря этому Gson получает полную информацию о типе (включая вложенные дженерики) через рефлексию.
- Не используйте
TypeToken.getParameterized()— это устаревший метод, который может работать некорректно с Kotlin. - Если вы используете ProGuard/R8, добавьте правила для сохранения TypeToken:
- Для Android-проектов с kotlinx.serialization рассмотрите альтернативы Gson, но если вы привязаны к Gson — описанный метод надёжен и проверен.
- Требуется создать экземпляр парсера — лишняя строчка кода.
- Не так лаконично, как
inline reified.
- >`. Подскажите, как правильно использовать TypeToken с Gson в Kotlin? Спасибо!
<<
Проблема: erased generics в JVM
Вы абсолютно правы: корень проблемы — стирание типов (type erasure) в Java/Kotlin. Во время выполнения JVM не знает, что List<User> — это список именно пользователей, для неё это просто List. Поэтому Gson не может создать экземпляры User и возвращает LinkedTreeMap (словарь), который является внутренним представлением JSON-объекта.
Решение: reified + inline + TypeToken
В Kotlin есть мощный механизм — reified type parameters. Он работает только с inline-функциями и позволяет сохранить информацию о типе во время компиляции. Вот правильный подход:
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
inline fun <reified T> parseList(json: String): List<T> {
// TypeToken создаёт объект, который "запоминает" тип T
val type = object : TypeToken<List<T>>() {}.type
return Gson().fromJson(json, type)
}
data class User(val name: String, val age: Int)
fun main() {
val json = """[{"name":"John","age":30}]"""
val users: List<User> = parseList(json) // Тип выводится автоматически
println(users[0].name) // John — всё работает!
}
Универсальное решение для любых дженериков
Часто нужно десериализовать не только список, но и другие обобщённые типы (например, Map<String, User> или кастомные классы). Создайте вспомогательную функцию:
inline fun <reified T> fromJson(json: String): T {
val type = object : TypeToken<T>() {}.type
return Gson().fromJson(json, type)
}
// Примеры использования:
fun main() {
val jsonList = """[{"name":"Alice"}]"""
val jsonMap = """{"key1":{"name":"Bob"}}"""
val jsonCustom = """{"data":[{"name":"Charlie"}],"error":null}"""
val users: List<User> = fromJson(jsonList)
val userMap: Map<String, User> = fromJson(jsonMap)
val result: Result<List<User>> = fromJson(jsonCustom)
println(users[0].name) // Alice
println(userMap["key1"]?.name) // Bob
println(result.data?.first()?.name) // Charlie
}
// Вспомогательный класс для примера
data class Result<T>(val data: T?, val error: String?)
Почему это работает?
Важные замечания
-keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.reflect.TypeToken
Этот подход — стандартный и рекомендуемый способ работы с дженериками в Gson для Kotlin. Он решает проблему LinkedTreeMap раз и навсегда.
Альтернатива: использование TypeToken через вспомогательный класс
Если вы по каким-то причинам не хотите использовать reified (например, ваш код не inline), можно создать вспомогательный класс-обёртку, который явно хранит тип:
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
class JsonParser<T>(private val type: Type) {
fun parse(json: String): T = Gson().fromJson(json, type)
companion object {
inline fun <reified T> create(): JsonParser<T> {
return JsonParser(object : TypeToken<T>() {}.type)
}
}
}
// Использование:
data class User(val name: String)
fun main() {
val json = """{"name":"John"}"""
val parser = JsonParser.create<User>()
val user = parser.parse(json)
println(user.name) // John
}
Минусы этого подхода
Дополнительный совет: избегайте raw-типов
Никогда не используйте Gson().fromJson(json, List::class.java) — это всегда приведёт к LinkedTreeMap. Всегда передавайте полный тип с помощью TypeToken или reified.
Для большинства случаев первый ответ (с inline reified fromJson) является лучшим решением — он компактный, универсальный и не требует создания дополнительных классов.
Ответов пока нет.
Войдите чтобы оставить ответ