Настройка экземпляра Json
Экземпляр Json по умолчанию строго соответствует спецификации JSON и объявлениям в классах Kotlin.
Вы можете использовать более гибкие возможности JSON или преобразования типов, создав пользовательский экземпляр Json с помощью функции-построителя Json():
// Creates a Json instance based on the default configuration, allowing special floating-point values
val customJson = Json {
allowSpecialFloatingPointValues = true
}
// Use the customJson instance with the same syntax as the default one to encode a string
val jsonString = customJson.encodeToString(Data(Double.NaN))
println(jsonString)
Экземпляры Json, созданные таким способом, неизменяемы и потокобезопасны, поэтому их можно безопасно хранить в свойстве верхнего уровня и использовать повторно.
Также можно создать новый экземпляр Json на основе существующего и изменить его настройки с помощью того же синтаксиса построителя:
// Creates a new instance based on an existing Json
val lenientJson = Json(customJson) {
isLenient = true
prettyPrint = true
}
Настройка структуры JSON
Вы можете настроить способ структурирования данных экземпляром Json при кодировании и декодировании. Это позволяет управлять тем, какие значения появляются в выходных данных и как представляются определённые типы.
Кодирование значений по умолчанию
По умолчанию кодировщик JSON не включает значения свойств по умолчанию, поскольку при декодировании они автоматически применяются к отсутствующим свойствам. Такое поведение особенно полезно для свойств, допускающих значение null и имеющих значение null по умолчанию, поскольку позволяет не записывать ненужные значения null. Подробнее см. раздел Управление сериализацией свойств со значениями по умолчанию.
Чтобы изменить это поведение по умолчанию, задайте свойству encodeDefaults значение true в экземпляре Json:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Configures a Json instance to encode default values
val format = Json { encodeDefaults = true }
@Serializable
class Project(
val name: String,
val language: String = "Kotlin",
val website: String? = null
)
fun main() {
val data = Project("kotlinx.serialization")
// Encodes all the property values, including the default ones
println(format.encodeToString(data))
// {"name":"kotlinx.serialization","language":"Kotlin","website":null}
}
//sampleEnd
Исключение явных значений null
По умолчанию все значения null кодируются в выходные данные JSON. Чтобы исключить значения null, задайте свойству explicitNulls значение false в экземпляре Json:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Configures a Json instance to omit null values during serialization
val format = Json { explicitNulls = false }
@Serializable
data class Project(
val name: String,
val language: String,
val version: String? = "1.2.2",
val website: String?,
val description: String? = null
)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin", null, null, null)
val json = format.encodeToString(data)
// Omits version, website, and description properties from the JSON output
println(json)
// {"name":"kotlinx.serialization","language":"Kotlin"}
// Treats missing nullable properties without defaults as null
// Fills properties that have defaults with their default values
println(format.decodeFromString<Project>(json))
// Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null)
}
//sampleEnd
Если для explicitNulls задано значение false, кодирование и декодирование могут стать асимметричными. В этом примере свойство version имеет значение null перед кодированием, но при декодировании получает значение 1.2.2.
Разрешение структурированных ключей карт
Формат JSON изначально не поддерживает карты со структурированными ключами, поскольку ключи JSON — это строки, представляющие только примитивы или перечисления. Чтобы сериализовать и десериализовать карты с ключами, заданными пользовательскими классами, используйте свойство allowStructuredMapKeys:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Configures a Json instance to encode maps with structured keys
val format = Json { allowStructuredMapKeys = true }
@Serializable
data class Project(val name: String)
fun main() {
val map = mapOf(
Project("kotlinx.serialization") to "Serialization",
Project("kotlinx.coroutines") to "Coroutines"
)
// Serializes the map with structured keys as a JSON array:
// [key1, value1, key2, value2,...]
println(format.encodeToString(map))
// [{"name":"kotlinx.serialization"},"Serialization",{"name":"kotlinx.coroutines"},"Coroutines"]
}
//sampleEnd
Разрешение специальных значений с плавающей точкой
По умолчанию специальные значения с плавающей точкой, такие как Double.NaN и бесконечности, не поддерживаются в JSON, поскольку спецификация JSON запрещает их.
Чтобы включить их кодирование и декодирование, задайте свойству allowSpecialFloatingPointValues значение true в экземпляре Json:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Configures a Json instance to allow special floating-point values
val format = Json { allowSpecialFloatingPointValues = true }
@Serializable
class Data(
val value: Double
)
fun main() {
val data = Data(Double.NaN)
// Produces a non-standard JSON output used for representing special floating-point values
println(format.encodeToString(data))
// {"value":NaN}
}
//sampleEnd
Задание дискриминатора класса для полиморфизма
При работе с полиморфными данными можно использовать свойство classDiscriminator, чтобы задать имя ключа, идентифицирующего тип сериализуемого полиморфного объекта. В сочетании с явным сериализуемым именем, заданным аннотацией @SerialName, этот подход позволяет полностью контролировать итоговую структуру JSON:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Configures a Json instance to use a custom class discriminator
val format = Json { classDiscriminator = "#class" }
@Serializable
sealed class Project {
abstract val name: String
}
// Specifies a custom serial name for the OwnedProject class
@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()
// Specifies a custom serial name for the SimpleProject class
@Serializable
@SerialName("simple")
class SimpleProject(override val name: String) : Project()
fun main() {
val simpleProject: Project = SimpleProject("kotlinx.serialization")
val ownedProject: Project = OwnedProject("kotlinx.coroutines", "kotlin")
// Serializes SimpleProject with #class: "simple"
println(format.encodeToString(simpleProject))
// {"#class":"simple","name":"kotlinx.serialization"}
// Serializes OwnedProject with #class: "owned"
println(format.encodeToString(ownedProject))
// {"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd
Свойство classDiscriminator экземпляра Json позволяет задать один ключ-дискриминатор для всех полиморфных типов, а экспериментальная аннотация @JsonClassDiscriminator предоставляет больше гибкости. Она позволяет задать пользовательский дискриминатор непосредственно в базовом классе, откуда он автоматически наследуется всеми подклассами.
Пример:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// The @JsonClassDiscriminator annotation is inheritable, so all subclasses of Base will have the same discriminator
@Serializable
@OptIn(ExperimentalSerializationApi::class)
@JsonClassDiscriminator("message_type")
sealed class Base
// Inherits the discriminator from Base
@Serializable
sealed class ErrorClass: Base()
// Defines a class that combines a message and an optional error
@Serializable
data class Message(val message: Base, val error: ErrorClass?)
@Serializable
@SerialName("my.app.BaseMessage")
data class BaseMessage(val message: String) : Base()
@Serializable
@SerialName("my.app.GenericError")
data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass()
val format = Json { classDiscriminator = "#class" }
fun main() {
val data = Message(BaseMessage("not found"), GenericError(404))
// Uses the discriminator from Base for all subclasses
println(format.encodeToString(data))
// {"message":{"message_type":"my.app.BaseMessage","message":"not found"},"error":{"message_type":"my.app.GenericError","error_code":404}}
}
//sampleEnd
Настройка режима вывода дискриминатора класса
Используйте свойство JsonBuilder.classDiscriminatorMode, чтобы управлять добавлением дискриминаторов классов в выходные данные JSON. По умолчанию дискриминатор добавляется только для полиморфных типов, что полезно при работе с иерархиями полиморфных классов.
Чтобы изменить это поведение, задайте свойству ClassDiscriminatorMode одно из следующих значений:
POLYMORPHIC: (по умолчанию) добавляет дискриминатор класса только для полиморфных типов.ALL_JSON_OBJECTS: добавляет дискриминатор класса ко всем объектам JSON, где это возможно.NONE: полностью исключает дискриминатор класса.
Пример со свойством ClassDiscriminatorMode, которому задано значение NONE:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Configures a Json instance to omit the class discriminator from the output
val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE }
@Serializable
sealed class Project {
abstract val name: String
}
@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()
fun main() {
val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
// Serializes without a discriminator
println(format.encodeToString(data))
// {"name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd
Форматирование
По умолчанию Json формирует компактный вывод в одну строку.
Для повышения удобочитаемости можно добавить отступы и переносы строк, включив форматирование выходных данных. Для этого задайте свойству prettyPrint значение true в экземпляре Json:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Creates a custom Json format
val format = Json { prettyPrint = true }
@Serializable
data class Project(val name: String, val language: String)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
// Prints the JSON output with line breaks and indentations
println(format.encodeToString(data))
}
//sampleEnd
Этот пример выводит следующий результат:
{
"name": "kotlinx.serialization",
"language": "Kotlin"
}
Настройка десериализации JSON
Парсер Json в Kotlin предоставляет несколько параметров, позволяющих настроить разбор и десериализацию данных JSON.
Игнорирование неизвестных ключей
При работе с данными JSON из сторонних сервисов или других динамических источников со временем в объекты JSON могут добавляться новые свойства.
По умолчанию неизвестные ключи (имена свойств во входных данных JSON) приводят к ошибке десериализации. Чтобы этого избежать, задайте свойству ignoreUnknownKeys значение true в экземпляре Json:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Creates a Json instance to ignore unknown keys
val format = Json { ignoreUnknownKeys = true }
@Serializable
data class Project(val name: String)
fun main() {
val data = format.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":"Kotlin"}
""")
// The language key is ignored because it's not in the Project class
println(data)
// Project(name=kotlinx.serialization)
}
//sampleEnd
Игнорирование неизвестных ключей для определённых классов
Вместо того чтобы включать ignoreUnknownKeys для всех классов, можно игнорировать неизвестные ключи только для определённых классов с помощью аннотации @JsonIgnoreUnknownKeys. Это позволяет по умолчанию сохранять строгую десериализацию и допускать более свободную обработку только там, где это необходимо.
Аннотация @JsonIgnoreUnknownKeys является экспериментальной. Чтобы разрешить её использование, примените аннотацию @OptIn(ExperimentalSerializationApi::class) или параметр компилятора -opt-in=kotlinx.serialization.ExperimentalSerializationApi:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@OptIn(ExperimentalSerializationApi::class)
@Serializable
// Unknown properties in Outer are ignored during deserialization
@JsonIgnoreUnknownKeys
data class Outer(val a: Int, val inner: Inner)
@Serializable
data class Inner(val x: String)
fun main() {
val outer = Json.decodeFromString<Outer>(
"""{"a":1,"inner":{"x":"value"},"unknownKey":42}"""
)
println(outer)
// Outer(a=1, inner=Inner(x=value))
// Throws an exception
// unknownKey inside inner is NOT ignored because Inner is not annotated
println(
Json.decodeFromString<Outer>(
"""{"a":1,"inner":{"x":"value","unknownKey":"unexpected"}}"""
)
)
}
//sampleEnd
В этом примере Inner выбрасывает SerializationException при наличии неизвестных ключей, поскольку он не аннотирован с помощью @JsonIgnoreUnknownKeys.
Приведение входных значений
При работе с данными JSON из сторонних сервисов или других динамических источников формат может изменяться. Это может приводить к исключениям при декодировании, если фактические значения не соответствуют ожидаемым типам.
Реализация Json по умолчанию строго проверяет типы входных данных. Чтобы ослабить это ограничение, задайте свойству coerceInputValues значение true в экземпляре Json:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
val format = Json { coerceInputValues = true }
@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
val data = format.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":null}
""")
// Coerces the invalid null value for language to its default value
println(data)
// Project(name=kotlinx.serialization, language=Kotlin)
}
//sampleEnd
Свойство coerceInputValues влияет только на декодирование. Оно обрабатывает некоторые недопустимые входные значения так, как если бы соответствующее свойство отсутствовало. В настоящее время это относится к следующим значениям:
nullдля типов, не допускающих nullнеизвестные значения перечислений
Если значение отсутствует, оно заменяется значением свойства по умолчанию, если оно задано.
Для перечислений значение заменяется на null, только если:
Значение по умолчанию не задано.
Для свойства
explicitNullsзадано значениеfalse.Свойство допускает значение null.
Можно объединить coerceInputValues со свойством explicitNulls для обработки недопустимых значений перечислений:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
enum class Color { BLACK, WHITE }
@Serializable
data class Brush(val foreground: Color = Color.BLACK, val background: Color?)
val json = Json {
coerceInputValues = true
explicitNulls = false
}
fun main() {
// Coerces the unknown foreground value to its default and background to null
val brush = json.decodeFromString<Brush>("""{"foreground":"pink", "background":"purple"}""")
println(brush)
// Brush(foreground=BLACK, background=null)
}
//sampleEnd
Разрешение завершающих запятых
Чтобы разрешить завершающие запятые во входных данных JSON, задайте свойству allowTrailingComma значение true:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Allows trailing commas in JSON objects and arrays
val format = Json { allowTrailingComma = true }
fun main() {
val numbers = format.decodeFromString<List<Int>>(
"""
[1, 2, 3,]
"""
)
println(numbers)
// [1, 2, 3]
}
//sampleEnd
Разрешение комментариев в JSON
Используйте свойство allowComments, чтобы разрешить комментарии во входных данных JSON. Если это свойство включено, парсер принимает следующие формы комментариев во входных данных:
//однострочные комментарии, заканчивающиеся символом новой строки\n/* */блочные комментарии
Пример:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Allows comments in JSON input
val format = Json { allowComments = true }
fun main() {
val numbers = format.decodeFromString<List<Int>>(
"""
[
// first element
1,
/* second element */
2
]
"""
)
println(numbers)
// [1, 2]
}
//sampleEnd
Нестрогий разбор
По умолчанию парсер Json соблюдает строгие правила JSON, обеспечивая соответствие спецификации RFC-8259, согласно которой ключи и строковые литералы должны быть заключены в кавычки.
Чтобы ослабить эти ограничения, задайте свойству isLenient значение true в экземпляре Json:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
val format = Json { isLenient = true }
enum class Status { SUPPORTED }
@Serializable
data class Project(val name: String, val status: Status, val votes: Int)
fun main() {
// Decodes a JSON string with lenient parsing
// Lenient parsing allows unquoted keys, string, and enum values
val data = format.decodeFromString<Project>("""
{
name : kotlinx.serialization,
status : SUPPORTED,
votes : "9000"
}
""")
println(data)
// Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000)
}
//sampleEnd
Настройка сопоставления имён между JSON и Kotlin
Некоторые данные JSON могут не полностью соответствовать соглашениям об именовании или ожидаемым форматам в Kotlin. Для решения этих задач библиотека сериализации Kotlin предоставляет несколько инструментов для управления расхождениями в именах, обработки нескольких имён свойств JSON и обеспечения единообразной стратегии именования сериализуемых данных.
Приём альтернативных имён свойств JSON для одного свойства Kotlin
Если имена свойств JSON меняются между версиями схемы, можно переименовать свойство JSON с помощью аннотации @SerialName.
Однако в этом случае невозможно декодировать данные, в которых всё ещё используются прежние имена свойств. Чтобы принимать альтернативные имена JSON для одного свойства, используйте аннотацию @JsonNames:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
// Maps both name and title JSON properties to the name property
data class Project(@JsonNames("title") val name: String)
fun main() {
val project = Json.decodeFromString<Project>("""{"name":"kotlinx.serialization"}""")
println(project)
// Project(name=kotlinx.serialization)
val oldProject = Json.decodeFromString<Project>("""{"title":"kotlinx.coroutines"}""")
// Both name and title Json properties correspond to name property
println(oldProject)
// Project(name=kotlinx.coroutines)
}
//sampleEnd
Регистронезависимое декодирование перечислений
Соглашения об именовании в Kotlin рекомендуют задавать имена значений перечислений прописными буквами с подчёркиваниями или в верхнем верблюжьем регистре. По умолчанию Json при декодировании использует точные имена констант перечислений Kotlin.
Однако в данных JSON из внешних источников могут использоваться имена в нижнем или смешанном регистре. Чтобы обрабатывать такие случаи, настройте экземпляр Json для регистронезависимого декодирования значений перечислений с помощью свойства JsonBuilder.decodeEnumsCaseInsensitive:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
// Configures a Json instance to decode enum values in a case-insensitive way
val format = Json { decodeEnumsCaseInsensitive = true }
enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B }
@Serializable
data class CasesList(val cases: List<Cases>)
fun main() {
// Decodes enum values regardless of their case, including alternative names
println(format.decodeFromString<CasesList>("""{"cases":["value_A", "alternative"]}"""))
// CasesList(cases=[VALUE_A, VALUE_B])
}
//sampleEnd
Применение глобальной стратегии именования
Если имена свойств во входных данных JSON отличаются от имён в Kotlin, можно явно задать имя каждого свойства с помощью аннотации @SerialName. Однако при переходе с других фреймворков или устаревшей кодовой базы может потребоваться одинаково преобразовать каждое сериализуемое имя.
В таких случаях можно задать глобальную стратегию именования с помощью свойства JsonBuilder.namingStrategy в экземпляре Json. Библиотека сериализации Kotlin предоставляет встроенные стратегии, например JsonNamingStrategy.SnakeCase:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
data class Project(val projectName: String, val projectOwner: String)
// Configures a Json instance to apply SnakeCase naming strategy
val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
fun main() {
val project = format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""")
// Serializes and deserializes as if all serial names are transformed from camel case to snake case
println(format.encodeToString(project.copy(projectName = "kotlinx.serialization")))
// {"project_name":"kotlinx.serialization","project_owner":"Kotlin"}
}
//sampleEnd
При использовании глобальной стратегии именования с JsonNamingStrategy учитывайте следующее:
Преобразование применяется ко всем свойствам, независимо от того, выведено ли сериализуемое имя из имени свойства или задано явно аннотацией
@SerialName. Нельзя исключить свойство из преобразования, задав сериализуемое имя. Чтобы сохранить без изменений определённые имена при сериализации, используйте вместо этого аннотацию@JsonNames.Если преобразованное имя конфликтует с другими преобразованными именами свойств или с альтернативными именами, заданными аннотацией
@JsonNames, десериализация завершится с исключением.Глобальные стратегии именования применяются неявно. Из-за этого по определению класса сложно определить сериализованные имена. Это может затруднить такие задачи, как Поиск использований и Переименование в IDE, а также полнотекстовый поиск с помощью таких инструментов, как
grep, что потенциально повышает риск ошибок и затраты на сопровождение.
Учитывая эти факторы, тщательно взвесьте все компромиссы перед внедрением глобальных стратегий именования в приложении.
Что дальше
Изучите расширенную обработку элементов JSON, чтобы изменять данные 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-json-configuration.html