Avatar ·

Как парсить JSON в Kotlin: лучшие библиотеки и примеры кода

Привет, коллеги! Я только начал изучать Kotlin после Java и столкнулся с типичной задачей — нужно распарсить JSON-строку в объект. В Java я использовал Gson или Jackson, но в Kotlin хочется сделать это красиво и идиоматично. У меня есть такой JSON от API:
{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}
Я пытался использовать встроенный JSONObject из Android SDK, но код получается ужасным:
val jsonString = """{"userId":1,"id":1,"title":"delectus aut autem","completed":false}"""
val jsonObject = JSONObject(jsonString)
val userId = jsonObject.getInt("userId")
val id = jsonObject.getInt("id")
val title = jsonObject.getString("title")
val completed = jsonObject.getBoolean("completed")
Это же просто ад! Для каждого поля нужно писать отдельный вызов, легко ошибиться в названии поля, да и типы нужно указывать вручную. А если JSON вложенный или содержит массивы — вообще кошмар. Подскажите, пожалуйста, как правильно парсить JSON в Kotlin? Какие библиотеки сейчас актуальны? Хочется, чтобы код был лаконичным, типобезопасным и с минимумом шаблонного кода. Может, есть что-то вроде data class с аннотациями? Буду благодарен за примеры! <<>>

Лучший способ: Kotlinx Serialization

Самое современное и идиоматичное решение для Kotlin — это kotlinx.serialization. Эта библиотека разрабатывается JetBrains специально для Kotlin, поддерживает мультиплатформенность и не требует рефлексии, что даёт отличную производительность.

Шаг 1: Подключение библиотеки

Добавьте в build.gradle.kts (или build.gradle):

// В секции plugins
plugins {
    kotlin("plugin.serialization") version "1.9.22"
}

// В секции dependencies
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}

Шаг 2: Определите data class

Создайте класс, соответствующий структуре JSON, и добавьте аннотацию @Serializable:

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class Todo(
    val userId: Int,
    val id: Int,
    val title: String,
    val completed: Boolean
)

Шаг 3: Парсинг одной строки

fun main() {
    val jsonString = """{"userId":1,"id":1,"title":"delectus aut autem","completed":false}"""
    
    val todo = Json.decodeFromString<Todo>(jsonString)
    
    println(todo.title) // delectus aut autem
    println(todo.completed) // false
}

Работа с массивами и вложенными объектами

Допустим, у вас есть такой JSON:

[
  {"userId":1, "id":1, "title":"Task 1", "completed":false},
  {"userId":1, "id":2, "title":"Task 2", "completed":true}
]

Парсинг массива:

val jsonArray = """[{"userId":1,...}, {"userId":2,...}]"""
val todos: List<Todo> = Json.decodeFromString<List<Todo>>(jsonArray)

Настройка парсера

Вы можете настроить поведение парсера через объект Json:

val json = Json {
    prettyPrint = true
    ignoreUnknownKeys = true  // игнорировать лишние поля
    isLenient = true          // допускать неточности в формате
    coerceInputValues = true  // приводить типы автоматически
}

// Использование:
val todo = json.decodeFromString<Todo>(jsonString)

Сериализация (обратный процесс)

Чтобы превратить объект обратно в JSON:

val todo = Todo(1, 1, "My task", false)
val jsonString = Json.encodeToString(todo)
println(jsonString) // {"userId":1,"id":1,"title":"My task","completed":false}

Почему это лучший выбор?

  • Типобезопасность — компилятор проверяет типы полей
  • Минимум кода — одна аннотация и один вызов функции
  • Производительность — без рефлексии, код генерируется на этапе компиляции
  • Мультиплатформенность — работает на JVM, JS, Native
  • Поддержка Kotlin — использует возможности языка: data class, sealed class, null safety

Альтернативы (если проект уже использует другую библиотеку)

Gson — если вы мигрируете с Java:

implementation("com.google.code.gson:gson:2.10.1")
// Использование:
val gson = Gson()
val todo = gson.fromJson(jsonString, Todo::class.java)

Jackson — мощный, но более тяжёлый:

implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1")
// Использование:
val mapper = jacksonObjectMapper()
val todo = mapper.readValue<Todo>(jsonString)

Но для чистого Kotlin-проекта я настоятельно рекомендую kotlinx.serialization — это современный стандарт.

<<>>

Альтернативный подход: ручной парсинг с JSONObject

Если вы не хотите подключать внешние библиотеки (например, в небольшом проекте или Android-приложении с минимальными зависимостями), можно использовать встроенный org.json.JSONObject из Android SDK или Java. Но код нужно писать аккуратнее.

Вот пример с использованием экстеншн-функций для удобства:

import org.json.JSONObject

fun JSONObject.toTodo(): Todo {
    return Todo(
        userId = getInt("userId"),
        id = getInt("id"),
        title = getString("title"),
        completed = getBoolean("completed")
    )
}

// Использование:
val jsonString = """{"userId":1,"id":1,"title":"delectus aut autem","completed":false}"""
val todo = JSONObject(jsonString).toTodo()

Для массивов можно сделать так:

import org.json.JSONArray

fun JSONArray.toTodoList(): List<Todo> {
    val list = mutableListOf<Todo>()
    for (i in 0 until length()) {
        list.add(getJSONObject(i).toTodo())
    }
    return list
}

Плюсы и минусы

  • Плюсы: не требует дополнительных зависимостей, подходит для простых случаев
  • Минусы: много шаблонного кода, нет проверки типов на этапе компиляции, легко получить исключение при изменении структуры JSON

Этот подход хорош для быстрых прототипов или когда JSON имеет простую и стабильную структуру. Для серьёзных проектов всё же рекомендую kotlinx.serialization или Gson.