# 变换 JSON 结构 To control the structure and content of the JSON generated during serialization, you can create [custom serializers](serialization-create-and-use-serializers.html#create-custom-serializers). For smaller adjustments, such as wrapping values or unwrapping arrays, the [JsonTransformingSerializer](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/) class provides a simpler way to modify JSON by working directly with the JSON element tree instead of using the [Encoder](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/) or [Decoder](https://kotlinlang.org/api/kotlinx.serialization/kotlinx.serialization-core/kotlinx.serialization.encoding/-decoder/) manually. > **Note:** > These sections are based on concepts explained in [Create and use serializers](serialization-create-and-use-serializers.html). If you're not familiar with custom serializers, we recommend reading that page first. The `JsonTransformingSerializer` is a JSON-specific abstract serializer that implements the [KSerializer](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) interface. It provides the [transformSerialize()](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/transform-serialize.html) and [transformDeserialize()](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/transform-deserialize.html) functions that you can override to adjust the JSON element tree before serialization or deserialization. In addition to transforming JSON structures, you can also use `JsonContentPolymorphicSerializer` to [select the appropriate polymorphic class based on the JSON content](#select-the-appropriate-polymorphic-class-based-on-the-json-content). ## Modify JSON structure You can adjust the JSON structure by transforming the JSON element tree. The following examples demonstrate common use cases, such as wrapping or unwrapping arrays and omitting specific properties. ### Wrap a single object in an array during deserialization Some APIs return a single JSON object for one item and a JSON array for multiple items. To deserialize both cases into a [List](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/): 1. Create a subclass of `JsonTransformingSerializer` and specify a serializer in its constructor. To use the standard conversion logic, pass the default serializer for the target type, such as [ListSerializer()](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-list-serializer.html) for lists. 2. Override the `transformDeserialize()` function. Here's an example: ```KOTLIN // 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 ) @Serializable data class User(val name: String) // Creates a serializer that transforms the results of the default List serializer object UserListSerializer : JsonTransformingSerializer>(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(""" {"name":"kotlinx.serialization","users":{"name":"kotlin"}} """)) // Project(name=kotlinx.serialization, users=[User(name=kotlin)]) println(Json.decodeFromString(""" {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]} """)) // Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)]) } //sampleEnd ``` ### Unwrap a single-element array during serialization To unwrap a single-element list into a single JSON object during serialization, override the `transformSerialize()` function: ```KOTLIN // 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 ) @Serializable data class User(val name: String) // Creates a serializer that transforms the results of the default List serializer object UserListSerializer : JsonTransformingSerializer>(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 ``` ### Omit specific properties during serialization If you can't [specify a default value](serialization-customization-options.html#set-default-values-for-optional-properties), but still want to omit a property when it has a specific value, use `JsonTransformingSerializer`: 1. Create a subclass of `JsonTransformingSerializer` and specify a serializer in its constructor. 2. Override the `transformSerialize()` function. Here's an example where the `Project` class has a `language` property, which is omitted when its value is `"Kotlin"`: ```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.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 ``` > **Note:** > When serializing an object directly, you need to explicitly pass the custom serializer to the `encodeToString()` function to ensure that the custom serialization logic is applied. For more information, see [Pass serializers manually](serialization-create-and-use-serializers.html#pass-a-serializer-manually). ## Select the appropriate polymorphic class based on the JSON content In [polymorphic serialization](serialization-polymorphism.html), JSON often contains a dedicated class discriminator property that identifies the concrete subtype during deserialization. If the JSON input doesn't contain a class discriminator, you can use [JsonContentPolymorphicSerializer](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/) to infer the type from the structure of the JSON. This serializer lets you override the `selectDeserializer()` function to choose the correct deserializer based on the JSON content. Here's an example where all values share a common base type with a `name` property: ```KOTLIN @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() ``` > **Note:** > In this example, the serializer chooses which subtype to use based on the JSON content, so you don't need a `sealed` class for the class hierarchy. To distinguish between `BasicProject` and `OwnedProject`, override the `selectDeserializer()` function. You can use this function to check whether the JSON object contains the `owner` key, and return the corresponding serializer: ```KOTLIN // Creates a custom serializer that selects deserializer based on the presence of "owner" object ProjectSerializer : JsonContentPolymorphicSerializer(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() } } ``` When you use this serializer to serialize data, Kotlin serialization uses the serializer of the value's actual runtime type. This could be the serializer [specified in a SerializersModule](serialization-polymorphism.html#serialize-closed-polymorphic-classes) or the default serializer: ```KOTLIN // 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::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 ``` ## Add custom behavior to the default serializer You can add custom behavior to the default serializer that Kotlin serialization generates, by using the default serializer as a delegate. To do so, annotate a serializable class with the [Experimental](components-stability.html#stability-levels-explained) [@KeepGeneratedSerializer](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-keep-generated-serializer/) and use the automatically generated `generatedSerializer()` as the base serializer in your custom `JsonTransformingSerializer`. Here's an example that updates the JSON structure during deserialization by combining multiple input properties into a single `name` property expected by the target class: ```KOTLIN // 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.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( """{"firstName":"John","lastName":"Smith"}""" ) println(fromExternalData) // User(name=John Smith) // Deserializes JSON where the name property matches the expected structure val fromInternalData = Json.decodeFromString( """{"name":"John Smith"}""" ) println(fromInternalData) // User(name=John Smith) } //sampleEnd ``` ## Implement custom serialization logic in JSON If the transformation functions provided by `JsonTransformingSerializer` or `JsonContentPolymorphicSerializer` aren't enough, you can implement custom serialization logic by defining your own [KSerializer](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) class. You can get full control over how values are serialized and deserialized by overriding the `serialize()` and `deserialize()` functions directly. When you implement custom serialization logic for JSON, you can cast `Encoder` to [JsonEncoder](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/) and `Decoder` to [JsonDecoder](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/) to call the JSON-specific functions [decodeJsonElement()](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/decode-json-element.html) and [encodeToJsonElement()](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html). These functions allow you to retrieve JSON elements from the value the decoder is currently handling, or insert JSON elements into it. Both `JsonDecoder` and `JsonEncoder` expose a `json` property that gives access to the active `Json` instance, which controls how values are encoded and decoded. Through that instance, you can use [encodeToJsonElement()](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html) and [decodeFromJsonElement()](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html) to convert between [JsonElement](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/) instances and Kotlin objects. Using these APIs, you can implement two-stage conversions: * Decode the input into a `JsonElement` first and then convert that element into a Kotlin value. * Convert a Kotlin value into a `JsonElement` first and then encode that element with the encoder. Let's look at an example of a custom `KSerializer` that fully controls how values of a `Response` type are encoded and decoded in JSON. This serializer encodes an `Ok` response directly as a JSON value and an `Error` response as a JSON object that contains the error message: ```KOTLIN // 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 { data class Ok(val data: T) : Response() data class Error(val message: String) : Response() } // Implements custom serialization logic for Response class ResponseSerializer( private val dataSerializer: KSerializer ) : KSerializer> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Response") { element("Ok", dataSerializer.descriptor) element("Error", buildClassSerialDescriptor("Error") { element("message") }) } // Deserializes a Response value from JSON override fun deserialize(decoder: Decoder): Response { // 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) { // 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>>(json)) // [Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)] } ``` ### Preserve unknown JSON attributes A common use case for a custom JSON-specific serializer is preserving JSON properties from the input that your serializable class doesn't define. By default, these properties are ignored during deserialization. To preserve these JSON properties, implement a custom JSON-specific serializer that collects all properties not defined in the target class into a dedicated [JsonObject](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-object/) field during deserialization. This allows you to preserve these properties in the serializable class without modifying the original JSON structure. Here's an example: ```KOTLIN // 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 { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") { element("name") element("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 ``` In this example, the preserved JSON properties remain at the same level within the input JSON object as the properties defined in the serializable class. ## What's next * Learn how to [serialize polymorphic classes](serialization-polymorphism.html) and handle objects of various types within a shared hierarchy. * Discover [other serialization formats](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/formats.md), such as CBOR and ProtoBuf.