404 Not Found

404 Not Found


nginx

Kotlin Serialization Explained

kotlinx.serialization generates serializers at compile time via a compiler plugin — Charlie's Order ↔ JSON conversion is zero-reflection, type-safe, and faster than Jackson. @SerialName handles field mapping in one line; Json { ignoreUnknownKeys = true } configures fault tolerance in one line.

1. What You'll Learn


2. A Real Developer's Story

(1) Pain Point: Reflection-Based Serialization Runtime Bombs

Bob used Jackson's reflection mode to serialize Order objects. During a refactor, orderId was renamed to id — JSON deserialization silently failed (field name mismatch), and 500 order records were lost.

(2) Compile-Time Serialization Solution

KOTLIN
// Jackson: reflection-based, errors at runtime
@JsonAlias("order_id")  // Easy to forget
data class Order(val orderId: String, ...)

// kotlinx.serialization: compile-time, errors at compile time
@Serializable
data class Order(
    @SerialName("order_id") val id: String,  // Compiler verifies!
    val total: Double
)

Compile-time serializer generation — field name changes become compile errors, not runtime data loss.


3. @Serializable and the Compiler Plugin

(1) Gradle Configuration

KOTLIN
// build.gradle.kts
plugins {
    kotlin("jvm") version "1.9.22"
    kotlin("plugin.serialization") version "1.9.22"  // Serialization compiler plugin
}

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

(2) Basic Serialization

KOTLIN
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

@Serializable
data class Order(val id: String, val total: Double, val status: String)

// Serialize: Object -> JSON String
val order = Order("ORD-001", 299.99, "CONFIRMED")
val json = Json.encodeToString(order)
// {"id":"ORD-001","total":299.99,"status":"CONFIRMED"}

// Deserialize: JSON String -> Object
val decoded = Json.decodeFromString<Order>(json)
// Order(id=ORD-001, total=299.99, status=CONFIRMED)

(3) Reflection vs Compile-Time Serialization

Dimension Jackson (Reflection) kotlinx.serialization
Mechanism Runtime reflection Compile-time code generation
Safety Runtime errors Compile-time errors
Performance Slower Faster (no reflection overhead)
ProGuard Requires keep rules Not needed
Multiplatform JVM only JVM / Native / JS

4. JSON Encoding and Decoding

(1) Json Configuration

KOTLIN
// Default: strict mode
val strictJson = Json  // Fails on unknown keys

// Lenient: ignore unknown keys (API versioning)
val lenientJson = Json {
    ignoreUnknownKeys = true        // Ignore fields not in class
    isLenient = true                // Accept malformed JSON
    encodeDefaults = true           // Include fields with default values
    prettyPrint = true              // Pretty print output
    prettyPrintIndent = "  "        // Indentation
    coerceInputValues = true        // Use default for null on non-null
}

val json = lenientJson.encodeToString(order)

(2) Json Configuration Options

Option Default Description
ignoreUnknownKeys false Ignore JSON fields not present in the class
isLenient false Lenient parsing (accept non-standard JSON)
encodeDefaults false Encode fields with default values
prettyPrint false Formatted output
coerceInputValues false Use default value when non-null field receives null

5. Field Mapping and Defaults

(1) @SerialName Field Mapping

KOTLIN
@Serializable
data class Order(
    @SerialName("order_id") val id: String,       // JSON: order_id
    @SerialName("order_total") val total: Double,  // JSON: order_total
    val status: String = "PENDING"                 // JSON: status (same name)
)

// JSON: {"order_id":"ORD-001","order_total":299.99,"status":"CONFIRMED"}

(2) Optional Fields and Defaults

KOTLIN
@Serializable
data class Order(
    @SerialName("order_id") val id: String,
    @SerialName("order_total") val total: Double,
    val status: String = "PENDING",               // Optional with default
    val customer: String? = null,                  // Nullable optional
    @SerialName("tax_rate") val taxRate: Double = 0.08
)

// Missing optional fields use defaults
val json = """{"order_id":"ORD-001","order_total":299.99}"""
val order = Json.decodeFromString<Order>(json)
// Order(id=ORD-001, total=299.99, status=PENDING, customer=null, taxRate=0.08)

(3) @Required to Force Fields

KOTLIN
@Serializable
data class Order(
    @Required val id: String,       // MUST be present in JSON
    @Required val total: Double     // MUST be present in JSON
)
// Missing 'id' or 'total' -> SerializationException

6. Multi-Format Support

KOTLIN
// ProtoBuf
import kotlinx.serialization.protobuf.ProtoBuf
val protoBytes = ProtoBuf.encodeToByteArray(order)
val fromProto = ProtoBuf.decodeFromByteArray<Order>(protoBytes)

// CBOR
import kotlinx.serialization.cbor.Cbor
val cborBytes = Cbor.encodeToByteArray(order)

// HOCON (configuration format)
import kotlinx.serialization.hocon.Hocon

(1) Format Comparison

Format Readability Size Speed Use Case
JSON High Large Medium API communication
ProtoBuf Low (binary) Small Fast High-performance RPC
CBOR Low (binary) Medium Fast IoT / embedded
HOCON High Medium Medium Configuration files

7. Serialization Encode/Decode Flow

100%
sequenceDiagram
    participant Obj as Order Object
    participant Ser as Serializer
    participant JSON as JSON String

    Note over Obj,Ser: Encode
    Obj->>Ser: @Serializable properties
    Ser->>JSON: encodeToString()
    Note over JSON: {"order_id":"ORD-001",...}

    Note over Ser,Obj: Decode
    JSON->>Ser: decodeFromString<Order>(json)
    Ser->>Obj: @Serializable constructor
    Note over Obj: Order(id=ORD-001,...)

8. Complete Example: OrderProcessor JSON Serialization

KOTLIN
// ============================================
// OrderProcessor - JSON Serialization
// Feature: Order <-> JSON with field mapping
// ============================================

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class Address(
    val street: String,
    val city: String,
    val country: String
)

@Serializable
data class OrderItem(
    val sku: String,
    val quantity: Int,
    @SerialName("unit_price") val unitPrice: Double
) {
    val subtotal: Double get() = quantity * unitPrice
}

@Serializable
data class Order(
    @SerialName("order_id") val id: String,
    @SerialName("order_total") val total: Double,
    val status: String = "PENDING",
    val customer: String? = null,
    val items: List<OrderItem> = emptyList(),
    val address: Address? = null,
    @SerialName("tax_rate") val taxRate: Double = 0.08,
    @SerialName("created_at") val createdAt: String = "2026-01-01T00:00:00Z"
)

val orderJson = Json {
    ignoreUnknownKeys = true
    encodeDefaults = true
    prettyPrint = true
    prettyPrintIndent = "  "
}

fun main() {
    // Create order
    val order = Order(
        id = "ORD-001",
        total = 299.99,
        status = "CONFIRMED",
        customer = "Alice",
        items = listOf(
            OrderItem("SKU-WIDGET", 3, 9.99),
            OrderItem("SKU-GADGET", 1, 149.99)
        ),
        address = Address("123 Main St", "New York", "US"),
        createdAt = "2026-07-13T10:30:00Z"
    )

    // Serialize: Order -> JSON
    println("=== Serialize ===")
    val jsonString = orderJson.encodeToString(order)
    println(jsonString)

    // Deserialize: JSON -> Order
    println("\n=== Deserialize ===")
    val decoded = orderJson.decodeFromString<Order>(jsonString)
    println("Order: ${decoded.id}, Total: \$${decoded.total} USD")
    println("Items: ${decoded.items.map { "${it.sku} x${it.quantity}" }}")

    // Handle unknown keys (API versioning)
    println("\n=== API Versioning ===")
    val jsonWithExtraFields = """
        {
          "order_id": "ORD-002",
          "order_total": 1500.00,
          "status": "SHIPPED",
          "unknown_field": "this is fine",
          "new_api_version": 2
        }
    """.trimIndent()
    val decodedWithExtra = orderJson.decodeFromString<Order>(jsonWithExtraFields)
    println("Decoded with extra fields: ${decodedWithExtra.id}")

    // Minimal JSON (required fields only)
    println("\n=== Minimal JSON ===")
    val minimalJson = """{"order_id":"ORD-003","order_total":45.50}"""
    val minimal = orderJson.decodeFromString<Order>(minimalJson)
    println("Minimal: ${minimal.id}, Status: ${minimal.status}, Customer: ${minimal.customer}")
}

Output:

TEXT
=== Serialize ===
{
  "order_id": "ORD-001",
  "order_total": 299.99,
  "status": "CONFIRMED",
  "customer": "Alice",
  "items": [
    {
      "sku": "SKU-WIDGET",
      "quantity": 3,
      "unit_price": 9.99
    },
    {
      "sku": "SKU-GADGET",
      "quantity": 1,
      "unit_price": 149.99
    }
  ],
  "address": {
    "street": "123 Main St",
    "city": "New York",
    "country": "US"
  },
  "tax_rate": 0.08,
  "created_at": "2026-07-13T10:30:00Z"
}

=== Deserialize ===
Order: ORD-001, Total: $299.99 USD
Items: [SKU-WIDGET x3, SKU-GADGET x1]

=== API Versioning ===
Decoded with extra fields: ORD-002

=== Minimal JSON ===
Minimal: ORD-003, Status: PENDING, Customer: null

❓ FAQ

Q Can kotlinx.serialization be used alongside Jackson?
A Yes, but it's not recommended. The two serialization mechanisms differ. During migration, you can use @JsonAlias as a bridge; for new projects, go directly with kotlinx.serialization.
Q Can sealed classes be serialized?
A Yes. @Serializable sealed classes automatically include a type discriminator ("type":"SubClassName"), and the correct subclass is chosen during deserialization.
Q How do I customize serialization logic?
A Specify a custom serializer with @Serializable(with = CustomSerializer::class), or implement the KSerializer<T> interface to manually control encoding/decoding.
Q Does serialization support generics?
A Yes, but you need to annotate the generic class with @Serializable. decodeFromString requires explicit type parameters.
Q Why is the compiler plugin needed?
A Kotlin's compiler plugin auto-generates serializer code for @Serializable classes at compile time, avoiding runtime reflection. This is the foundation of its performance and safety.
Q What's the difference between @SerialName and @JsonProperty?
A Functionally the same (JSON field name mapping), but @SerialName is kotlinx.serialization's annotation processed at compile time; @JsonProperty is Jackson's annotation processed at runtime via reflection.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Add @Serializable to Order, serialize to JSON, and deserialize back. Hint: @Serializable data class Order(...)
  2. Intermediate (⭐⭐): Use @SerialName to map Order field names (order_id, order_total), and configure Json { ignoreUnknownKeys = true } for API evolution. Hint: Reference Section 5
  3. Challenge (⭐⭐⭐): Implement a custom serializer that flattens Order items into a flat JSON format (expanding items to item_1_sku, item_1_qty format). Hint: Implement KSerializer<Order>

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏