Преобразование структуры JSON
Чтобы управлять структурой и содержимым JSON, создаваемого при сериализации, можно создать пользовательские сериализаторы. Для небольших изменений, например оборачивания значений или разворачивания массивов, класс JsonTransformingSerializer предлагает более простой способ изменить JSON, напрямую работая с деревом элементов JSON, а не вручную используя Encoder или Decoder.
JsonTransformingSerializer — это абстрактный сериализатор, предназначенный для работы с JSON и реализующий интерфейс KSerializer. Он предоставляет функции transformSerialize() и transformDeserialize(), которые можно переопределить, чтобы изменить дерево элементов JSON перед сериализацией или десериализацией.
Помимо преобразования структур JSON, можно также использовать JsonContentPolymorphicSerializer, чтобы выбрать подходящий полиморфный класс на основе содержимого JSON.
Изменение структуры JSON
Структуру JSON можно изменить, преобразовав дерево элементов JSON. В следующих примерах показаны распространённые случаи использования: оборачивание или разворачивание массивов и исключение определённых свойств.
Оборачивание одиночного объекта в массив при десериализации
Некоторые API возвращают один объект JSON для одного элемента и массив JSON для нескольких элементов. Чтобы десериализовать оба варианта в List:
Создайте подкласс
JsonTransformingSerializerи укажите сериализатор в его конструкторе. Чтобы использовать стандартную логику преобразования, передайте сериализатор по умолчанию для целевого типа, напримерListSerializer()для списков.Переопределите функцию
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:
Создайте подкласс
JsonTransformingSerializerи укажите сериализатор в его конструкторе.Переопределите функцию
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
Выбор подходящего полиморфного класса на основе содержимого 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()
Чтобы различать 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.
© 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