Spec-Zone.ru › Kotlin 2

Преобразование структуры JSON

Чтобы управлять структурой и содержимым JSON, создаваемого при сериализации, можно создать пользовательские сериализаторы. Для небольших изменений, например оборачивания значений или разворачивания массивов, класс JsonTransformingSerializer предлагает более простой способ изменить JSON, напрямую работая с деревом элементов JSON, а не вручную используя Encoder или Decoder.

В этих разделах рассматриваются понятия, описанные в статье Создание и использование сериализаторов. Если вы не знакомы с пользовательскими сериализаторами, рекомендуем сначала прочитать эту статью.

JsonTransformingSerializer — это абстрактный сериализатор, предназначенный для работы с JSON и реализующий интерфейс KSerializer. Он предоставляет функции transformSerialize() и transformDeserialize(), которые можно переопределить, чтобы изменить дерево элементов JSON перед сериализацией или десериализацией.

Помимо преобразования структур JSON, можно также использовать JsonContentPolymorphicSerializer, чтобы выбрать подходящий полиморфный класс на основе содержимого JSON.

Изменение структуры JSON

Структуру JSON можно изменить, преобразовав дерево элементов JSON. В следующих примерах показаны распространённые случаи использования: оборачивание или разворачивание массивов и исключение определённых свойств.

Оборачивание одиночного объекта в массив при десериализации

Некоторые API возвращают один объект JSON для одного элемента и массив JSON для нескольких элементов. Чтобы десериализовать оба варианта в List:

  1. Создайте подкласс JsonTransformingSerializer и укажите сериализатор в его конструкторе. Чтобы использовать стандартную логику преобразования, передайте сериализатор по умолчанию для целевого типа, например ListSerializer() для списков.

  2. Переопределите функцию transformDeserialize().

Пример:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
data class Project(
    val name: String,
    // Specifies UserListSerializer to handle the serialization of the users property
    @Serializable(with = UserListSerializer::class)
    val users: List<User>
)

@Serializable
data class User(val name: String)

// Creates a serializer that transforms the results of the default List serializer
object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) {
    override fun transformDeserialize(element: JsonElement): JsonElement =
        // If the element is not a JsonArray, wraps it into a single-element array
        if (element !is JsonArray) JsonArray(listOf(element)) else element
}

fun main() {
    println(Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","users":{"name":"kotlin"}}
    """))
    // Project(name=kotlinx.serialization, users=[User(name=kotlin)])
   
    println(Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]}
    """))
    // Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)])
}
//sampleEnd

Разворачивание массива с одним элементом при сериализации

Чтобы при сериализации преобразовать список с одним элементом в одиночный объект JSON, переопределите функцию transformSerialize():

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
data class Project(
    val name: String,
    // Specifies UserListSerializer to handle serialization of the users property
    @Serializable(with = UserListSerializer::class)
    val users: List<User>
)

@Serializable
data class User(val name: String)

// Creates a serializer that transforms the results of the default List serializer
object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) {

    override fun transformSerialize(element: JsonElement): JsonElement {
        require(element is JsonArray)
        // Unwraps single-element lists into a single JSON object
        return element.singleOrNull() ?: element
    }
}
  
fun main() {
    val data = Project("kotlinx.serialization", listOf(User("kotlin")))
    println(Json.encodeToString(data))
    // {"name":"kotlinx.serialization","users":{"name":"kotlin"}}
}
//sampleEnd

Исключение определённых свойств при сериализации

Если вы не можете указать значение по умолчанию, но всё же хотите исключить свойство, когда оно имеет определённое значение, используйте JsonTransformingSerializer:

  1. Создайте подкласс JsonTransformingSerializer и укажите сериализатор в его конструкторе.

  2. Переопределите функцию transformSerialize().

Вот пример, в котором класс Project содержит свойство language, исключаемое, если его значение равно "Kotlin":

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
class Project(val name: String, val language: String)

// Creates a custom serializer that omits the language property if it's equal to "Kotlin"
object ProjectSerializer : JsonTransformingSerializer<Project>(Project.serializer()) {
    override fun transformSerialize(element: JsonElement): JsonElement =
        // Omits the language property if its value is "Kotlin"
        JsonObject(element.jsonObject.filterNot {
            (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin"
        })
}

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")

    // Uses the default serializer
    println(Json.encodeToString(data))
    // {"name":"kotlinx.serialization","language":"Kotlin"}

    // Applies the custom serializer to omit the language property 
    println(Json.encodeToString(ProjectSerializer, data))
    // {"name":"kotlinx.serialization"}
}
//sampleEnd

При прямой сериализации объекта необходимо явно передать пользовательский сериализатор в функцию encodeToString(), чтобы применялась пользовательская логика сериализации. Подробнее см. в разделе Передача сериализаторов вручную.

Выбор подходящего полиморфного класса на основе содержимого JSON

При полиморфной сериализации JSON часто содержит специальное свойство дискриминатора класса, которое при десериализации определяет конкретный подтип.

Если входные данные JSON не содержат дискриминатор класса, можно использовать JsonContentPolymorphicSerializer, чтобы определить тип по структуре JSON. Этот сериализатор позволяет переопределить функцию selectDeserializer() и выбрать подходящий десериализатор на основе содержимого JSON.

Вот пример, в котором все значения имеют общий базовый тип со свойством name:

@Serializable
abstract class Project {
    abstract val name: String
}

@Serializable
data class BasicProject(override val name: String): Project()

@Serializable
data class OwnedProject(override val name: String, val owner: String) : Project()

В этом примере сериализатор выбирает подтип на основе содержимого JSON, поэтому для иерархии классов не требуется класс sealed.

Чтобы различать BasicProject и OwnedProject, переопределите функцию selectDeserializer(). С помощью этой функции можно проверить, содержит ли объект JSON ключ owner, и вернуть соответствующий сериализатор:

// Creates a custom serializer that selects deserializer based on the presence of "owner"
object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
    override fun selectDeserializer(element: JsonElement) = when {
        // Selects the OwnedProject serializer if the JSON object contains an "owner" key
        "owner" in element.jsonObject -> OwnedProject.serializer()
        else -> BasicProject.serializer()
    }
}

Когда этот сериализатор используется для сериализации данных, Kotlin serialization применяет сериализатор фактического типа значения во время выполнения. Это может быть сериализатор, указанный в SerializersModule, или сериализатор по умолчанию:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*


@Serializable
abstract class Project {
    abstract val name: String
}

@Serializable
data class BasicProject(override val name: String): Project()

@Serializable
data class OwnedProject(override val name: String, val owner: String) : Project()

// Creates a custom serializer that selects deserializer based on the presence of "owner"
object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
    override fun selectDeserializer(element: JsonElement) = when {
        // Selects the OwnedProject serializer if the JSON object contains an "owner" key
        "owner" in element.jsonObject -> OwnedProject.serializer()
        else -> BasicProject.serializer()
    }
}

//sampleStart
fun main() {
    val data = listOf(
        OwnedProject("kotlinx.serialization", "kotlin"),
        BasicProject("example")
    )
    // No class discriminator in the JSON output
    val string = Json.encodeToString(ListSerializer(ProjectSerializer), data)

    println(string)
    // [{"name":"kotlinx.serialization","owner":"kotlin"},{"name":"example"}]

    println(Json.decodeFromString(ListSerializer(ProjectSerializer), string))
    // [OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)]
}
//sampleEnd

Добавление пользовательского поведения к сериализатору по умолчанию

Можно добавить пользовательское поведение к сериализатору по умолчанию, который генерирует Kotlin serialization, используя этот сериализатор в качестве делегата.

Для этого аннотируйте сериализуемый класс аннотацией Experimental @KeepGeneratedSerializer и используйте автоматически сгенерированный generatedSerializer() в качестве базового сериализатора в своём пользовательском JsonTransformingSerializer.

Вот пример обновления структуры JSON при десериализации: несколько входных свойств объединяются в одно свойство name, требуемое целевому классу:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
@OptIn(ExperimentalSerializationApi::class)
@KeepGeneratedSerializer
@Serializable(with = UserNameSerializer::class)
// Defines a type with a name property
data class User(val name: String)

// Adds custom logic to the default serializer to combine input properties during deserialization
object UserNameSerializer : JsonTransformingSerializer<User>(User.generatedSerializer()) {
    override fun transformDeserialize(element: JsonElement): JsonElement {
        val jsonObject = element.jsonObject
        val first = jsonObject["firstName"]?.jsonPrimitive?.content
        val last = jsonObject["lastName"]?.jsonPrimitive?.content

        // Combines input properties into the name property
        // if the input doesn't match the expected structure
        return if (first != null && last != null) {
            JsonObject(mapOf("name" to JsonPrimitive("$first $last")))
        } else {
            jsonObject
        }
    }
}

fun main() {
    // Deserializes JSON where the name property is split across multiple input properties
   val fromExternalData = Json.decodeFromString<User>(
        """{"firstName":"John","lastName":"Smith"}"""
    )
    println(fromExternalData)
    // User(name=John Smith)

    // Deserializes JSON where the name property matches the expected structure
    val fromInternalData = Json.decodeFromString<User>(
        """{"name":"John Smith"}"""
    )
    println(fromInternalData)
    // User(name=John Smith)
}
//sampleEnd

Реализация пользовательской логики сериализации в JSON

Если функций преобразования, предоставляемых JsonTransformingSerializer или JsonContentPolymorphicSerializer, недостаточно, можно реализовать собственную логику сериализации, определив свой класс KSerializer.

Чтобы получить полный контроль над сериализацией и десериализацией значений, переопределите функции serialize() и deserialize() напрямую.

При реализации пользовательской логики сериализации для JSON можно привести Encoder к типу JsonEncoder, а Decoder — к типу JsonDecoder, чтобы вызывать специфичные для JSON функции decodeJsonElement() и encodeToJsonElement(). Эти функции позволяют извлекать элементы JSON из значения, обрабатываемого декодером в данный момент, или вставлять в него элементы JSON.

И JsonDecoder, и JsonEncoder предоставляют свойство json, через которое доступен активный экземпляр Json, управляющий кодированием и декодированием значений. С его помощью можно использовать encodeToJsonElement() и decodeFromJsonElement() для преобразования экземпляров JsonElement в объекты Kotlin и обратно.

С помощью этих API можно реализовать двухэтапные преобразования:

  • Сначала декодировать входные данные в JsonElement, а затем преобразовать этот элемент в значение Kotlin.

  • Сначала преобразовать значение Kotlin в JsonElement, а затем закодировать этот элемент с помощью кодировщика.

Рассмотрим пример пользовательского KSerializer, который полностью управляет кодированием и декодированием значений типа Response в JSON. Этот сериализатор кодирует ответ Ok непосредственно как значение JSON, а ответ Error — как объект JSON, содержащий сообщение об ошибке:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*

// Defines a sealed class for API responses
@Serializable(with = ResponseSerializer::class)
sealed class Response<out T> {
    data class Ok<out T>(val data: T) : Response<T>()
    data class Error(val message: String) : Response<Nothing>()
}

// Implements custom serialization logic for Response
class ResponseSerializer<T>(
    private val dataSerializer: KSerializer<T>
) : KSerializer<Response<T>> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Response") {
        element("Ok", dataSerializer.descriptor)
        element("Error", buildClassSerialDescriptor("Error") {
            element<String>("message")
        })
    }
    // Deserializes a Response value from JSON
    override fun deserialize(decoder: Decoder): Response<T> {
        // Ensures that the decoder is a JsonDecoder
        require(decoder is JsonDecoder)

        // Decodes the input into a JsonElement
        val element = decoder.decodeJsonElement()

        // Converts the JsonElement into the corresponding Response value
        return if (element is JsonObject && "error" in element) {
            Response.Error(element["error"]!!.jsonPrimitive.content)
        } else {
            Response.Ok(
                decoder.json.decodeFromJsonElement(dataSerializer, element)
            )
        }
    }

    // Serializes a Response value to JSON
    override fun serialize(encoder: Encoder, value: Response<T>) {
        // Ensures that the encoder is a JsonEncoder
        require(encoder is JsonEncoder)

        // Converts the Response value into a JsonElement
        val element = when (value) {
            is Response.Ok ->
                encoder.json.encodeToJsonElement(dataSerializer, value.data)
            is Response.Error ->
                buildJsonObject { put("error", value.message) }
        }

        // Encodes the JsonElement using the encoder
        encoder.encodeJsonElement(element)
    }
}

@Serializable
data class Project(val name: String)

fun main() {
    val responses = listOf(
        Response.Ok(Project("kotlinx.serialization")),
        Response.Error("Not found")
    )

    val json = Json.encodeToString(responses)
    println(json)
    // [{"name":"kotlinx.serialization"},{"error":"Not found"}]

    println(Json.decodeFromString<List<Response<Project>>>(json))
    // [Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)]
}

Сохранение неизвестных атрибутов JSON

Распространённый сценарий использования специализированного для JSON пользовательского сериализатора — сохранение свойств JSON из входных данных, которые не определены в сериализуемом классе. По умолчанию такие свойства игнорируются при десериализации.

Чтобы сохранить эти свойства JSON, реализуйте специализированный для JSON пользовательский сериализатор, который при десериализации собирает все свойства, не определённые в целевом классе, в отдельное поле JsonObject. Это позволяет сохранить эти свойства в сериализуемом классе, не изменяя исходную структуру JSON.

Пример:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*

//sampleStart
data class UnknownProject(val name: String, val details: JsonObject)

object UnknownProjectSerializer : KSerializer<UnknownProject> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") {
        element<String>("name")
        element<JsonElement>("details")
    }

    override fun deserialize(decoder: Decoder): UnknownProject {
        // Ensures the decoder is JSON-specific
        val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON")

        // Reads the entire content as JSON
        val json = jsonInput.decodeJsonElement().jsonObject

        // Extracts and removes the name property
        val name = json.getValue("name").jsonPrimitive.content
        val details = json.toMutableMap()
        details.remove("name")
        return UnknownProject(name, JsonObject(details))
    }

    override fun serialize(encoder: Encoder, value: UnknownProject) {
        error("Serialization is not supported")
    }
}

fun main() {
    // Deserializes JSON with properties not defined in the serializable class into UnknownProject
    println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}"""))
    // UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"})

}
//sampleEnd

В этом примере сохранённые свойства JSON остаются на том же уровне во входном объекте JSON, что и свойства, определённые в сериализуемом классе.

Что дальше

  • Узнайте, как сериализовать полиморфные классы и обрабатывать объекты разных типов в общей иерархии.

  • Познакомьтесь с другими форматами сериализации, такими как CBOR и ProtoBuf.

10 июня 2026 г.
Элементы JSONСериализация JSON с помощью источников ввода-вывода

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/serialization-transform-json.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API