kotlinx.serialization

repository·master·Indexed 26 days ago

https://github.com/kotlin/kotlinx.serialization

A Kotlin multiplatform, multi-format, reflectionless serialization library. It uses a compiler plugin to generate visitor code for classes marked with @Serializable, enabling efficient serialization and deserialization across JVM, JS, and Native platforms. The library supports JSON and other formats, providing annotations like @SerialName, @Transient, and @EncodeDefault to control serialization behavior, and integrates with Gradle and Maven.

Tokens
31.3K
Snippets
104
Records
129
Agent score
91%

What's inside kotlinx.serialization

  1. Overview of Kotlin Serialization

    master

    Kotlin Serialization is a cross-platform, multi-format framework designed for data serialization. It converts trees of objects into strings, byte arrays, or other serial representations and back. The framework fully supports and enforces the Kotlin type system to ensure that only valid objects are deserialized.

    Note that Kotlin Serialization is not merely a library; it is a compiler plugin bundled with the Kotlin compiler distribution. Proper build configuration is required to enable its functionality.

  2. Identify available kotlinx.serialization modules

    master

    The kotlinx.serialization library is divided into several modules depending on your required format and integration needs:

    • kotlinx-serialization-core: Core API, standard library serializers, and basic JSON implementation.
    • kotlinx-serialization-json: Stable JSON implementation, JsonElement API for JSON trees, and JSON-specific serializers.
    • kotlinx-serialization-cbor: CBOR (Concise Binary Object Representation) implementation per RFC 8949.
    • kotlinx-serialization-protobuf: Protocol Buffers serialization format.
    • kotlinx-serialization-hocon: Deserialization of Lightbend Config objects into Kotlin objects.
    • kotlinx-serialization-properties: Converts Kotlin class hierarchies to flat key-value structures (Java Properties style).

    Experimental Integration Modules:

    • kotlinx-serialization-json-okio: Integration with the Okio library.
    • kotlinx-serialization-json-io: Integration with the kotlinx-io library.
  3. Kotlin Serialization Guide Overview

    master

    The Kotlin Serialization guide provides comprehensive instructions for using the kotlinx.serialization library. The guide is organized into several chapters covering:

    • Basic Serialization: Fundamentals of encoding/decoding, serializable classes, property requirements, and generic classes.
    • Builtin Classes: How to handle primitives (numbers, enums), composites (lists, maps, pairs), and special types like Unit or Duration.
    • Serializers: Deep dive into plugin-generated serializers, custom serializers (primitive, delegating, surrogate), and contextual serialization.
    • Polymorphism: Implementing closed polymorphism (sealed classes) and open polymorphism (interfaces and registered subclasses).
    • JSON Features: Detailed configuration for JSON (pretty printing, lenient parsing, unknown keys) and working with JsonElement builders.
    • Alternative and Custom Formats: Experimental support for CBOR, ProtoBuf, and creating your own custom formats.
    • Value Classes: Specific guidance on serializing value classes.
  4. Understand the role of serializers

    master
    In kotlinx.serialization, while formats (like JSON) control the encoding of an object into bytes, a serializer controls how an object is decomposed into its constituent properties. You can use automatically-derived serializers by marking classes with the @Serializable annotation, or use built-in serializers for primitive and collection types.
  5. Setup kotlinx.serialization with Gradle

    master

    Setting up kotlinx.serialization in Gradle requires two steps: applying the serialization compiler plugin and adding the runtime library dependency.

    1. Setting up the serialization plugin

    Use the Gradle plugins DSL. The plugin version should match your Kotlin version.

    Kotlin DSL:

    plugins {
        kotlin("jvm") version "2.3.20" // or kotlin("multiplatform")
        kotlin("plugin.serialization") version "2.3.20"
    }

    Groovy DSL:

    plugins {
        id 'org.jetbrains.kotlin.multiplatform' version '2.3.20'
        id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.20'
    }

    2. Adding the JSON library dependency

    Add the runtime library dependency. Note that the runtime library versioning is independent of the compiler plugin version.

    Kotlin DSL:

    dependencies {
        implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
    }

    Groovy DSL:

    dependencies {
        implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0"
    }

    Note: You can also use kotlinx-serialization-core if you want the core API without a specific format like JSON.

    plugins {
        kotlin("jvm") version "2.3.20"
        kotlin("plugin.serialization") version "2.3.20"
    }
    
    dependencies {
        implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
    }
  6. Simultaneously use plugin-generated and custom serializers

    master

    You can use a custom serializer for a class while still allowing the serialization plugin to generate a default one. This is useful for fallback strategies, accessing the type structure via KSerializer.descriptor, or providing default behavior for descendants.

    To do this, annotate your class with @KeepGeneratedSerializer. The plugin-generated serializer can then be accessed via the .generatedSerializer() function on the class's companion object.

    Requirements & Limitations:

    • Requires Kotlin 2.0.20 or higher.
    • The @KeepGeneratedSerializer annotation is experimental.
    • It is not allowed on classes involved in polymorphic serialization (interfaces, sealed classes, abstract classes, or classes marked with @Polymorphic).
    @OptIn(ExperimentalSerializationApi::class)
    @KeepGeneratedSerializer
    @Serializable(with = ColorAsStringSerializer::class)
    class Color(val rgb: Int)
    
    fun main() {
        val green = Color(0x00ff00)
        // Uses the custom ColorAsStringSerializer
        println(Json.encodeToString(green)) 
        
        // Uses the plugin-generated serializer
        println(Json.encodeToString(Color.generatedSerializer(), green)) 
    }
  7. Implement ProtoBuf 'oneof' fields using @ProtoOneOf

    master

    Kotlin Serialization supports ProtoBuf oneof fields using polymorphism. To implement this:

    1. Declare a sealed interface or abstract class to represent the oneof group (the "oneof interface").
    2. In your main message class, add a property of the oneof interface type and annotate it with @ProtoOneOf. Do not use @ProtoNumber on this property.
    3. Declare subclasses for the oneof interface, one for each element in the group. Each subclass must have exactly one property representing the element type.
    4. Annotate the property in each subclass with @ProtoNumber corresponding to the original .proto definition.
    // The outer class
    @OptIn(ExperimentalSerializationApi::class)
    @Serializable
    data class Data(
        @ProtoNumber(1) val name: String,
        @ProtoOneOf val phone: IPhoneType?,
    )
    
    // The oneof interface
    @Serializable sealed interface IPhoneType
    
    // Message holder for home_phone
    @OptIn(ExperimentalSerializationApi::class)
    @Serializable @JvmInline value class HomePhone(@ProtoNumber(2) val number: String): IPhoneType
    
    // Message holder for work_phone
    @OptIn(ExperimentalSerializationApi::class)
    @Serializable data class WorkPhone(@ProtoNumber(3) val number: String): IPhoneType
  8. Configure custom Json instances

    master
    The default Json implementation is strict. To support non-standard JSON features, create a custom Json instance using the Json {} builder. Custom instances are immutable, thread-safe, and should be stored and reused (e.g., in a top-level property) to benefit from internal caching of serialization information.
  9. Serialize Lists and Sets

    master

    Kotlin List and Set collections are supported. In JSON, both are represented as standard JSON arrays. During deserialization, the resulting type is determined by the static type specified in the source code (e.g., the property type or the type parameter of the decoding function).

    @Serializable
    data class Data(
        val a: List<Int>,
        val b: Set<Int>
    )
         
    fun main() {
        val data = Json.decodeFromString<Data>("""
            {
                "a": [42, 42],
                "b": [42, 42]
            }
        """)
        println(data)
    }
  10. Implement a Custom Serializer for Generic Types

    master

    A custom serializer for a generic class (e.g., Box<T>) must be a class (not an object) because it needs to accept KSerializer instances for its generic parameters in its constructor.

    @Serializable(with = BoxSerializer::class)
    data class Box<T>(val contents: T)
    
    class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> {
        override val descriptor: SerialDescriptor = SerialDescriptor("my.app.Box", dataSerializer.descriptor)
        override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents)
        override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder))
    }
  11. Implement a custom format using AbstractEncoder and AbstractDecoder

    master

    To create a custom serialization format, implement the Encoder and Decoder interfaces. For convenience, use the AbstractEncoder and AbstractDecoder skeleton implementations.

    In AbstractEncoder, most encodeXxx methods delegate to encodeValue(value: Any), which is the primary method you must implement to create a basic working format.

    When implementing an encoder for consumption by other parts of an application, it is recommended to propagate the @ExperimentalSerializationApi annotation rather than opting-in locally.

    @ExperimentalSerializationApi
    class ListEncoder : AbstractEncoder() {
        val list = mutableListOf<Any>()
    
        override val serializersModule: SerializersModule = EmptySerializersModule()
    
        override fun encodeValue(value: Any) {
            list.add(value)
        }
    }