kotlinx.serialization: nó là gì, chú thích và tuần tự hóa JSON

Tác giả: IT Sectr Đã đăng: 2026-03-15 Thời gian đọc: 12 phút

kotlinx.serialization — thư viện đa nền tảng từ JetBrains để chuyển đổi các đối tượng Kotlin thành JSON, ProtoBuf, CBOR và các định dạng khác mà không cần sử dụng phản xạ. Khác với Gson và Moshi, nó tạo mã trình tuần tự hóa tại thời điểm biên dịch thông qua chú thích @Serializable, mang lại hiệu suất cao và an toàn kiểu. Theo GitHub Kotlin/kotlinx.serialization, thư viện hỗ trợ Kotlin/JVM, Kotlin/Native, Kotlin/JS và Kotlin/Wasm.

Các điểm chính

  • kotlinx.serialization — tuần tự hóa tại thời điểm biên dịch: mã được tạo ra khi biên dịch, không sử dụng phản xạ
  • @Serializable — chú thích chính kích hoạt việc tạo trình tuần tự hóa cho một lớp
  • Json {} builder — cấu hình JSON qua Json { ignoreUnknownKeys = true; prettyPrint = true }
  • Đa nền tảng — thư viện hoạt động trên JVM, Native, JS và Wasm mà không thay đổi API
  • Trình tuần tự hóa tùy chỉnh — thông qua giao diện KSerializer cho các định dạng dữ liệu không chuẩn

kotlinx.serialization là gì

kotlinx.serialization là thư viện tuần tự hóa tích hợp cho Kotlin, được JetBrains phát triển như một phần của hệ sinh thái Kotlin chính thức. Sự khác biệt chính so với các giải pháp bên thứ ba (Gson, Moshi, Jackson) là nó không sử dụng phản xạ trong thời gian chạy. Thay vào đó, mã trình tuần tự hóa được tạo ra tại thời điểm biên dịch bằng Kotlin Symbol Processing (KSP) hoặc trình cắm biên dịch Kotlin. Điều này mang lại hiệu suất cao hơn 3–5 lần so với Gson và đảm bảo an toàn kiểu.

Thư viện chính thức hỗ trợ bốn định dạng: JSON (qua mô-đun kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) và HOCON (kotlinx-serialization-hocon). Các định dạng được thêm dưới dạng phụ thuộc riêng biệt trong build.gradle.kts, tránh kéo các thư viện không cần thiết vào dự án. Mỗi định dạng có bộ tham số cấu hình riêng.

Đa nền tảng là tính năng chính của thư viện. Cùng một lớp với @Serializable hoạt động trên tất cả các nền tảng: JVM (Android, Backend), Native (iOS), JS (Web, React) và Wasm (WebAssembly). Nhà phát triển không cần viết các triển khai tuần tự hóa khác nhau cho mỗi nền tảng — mã vẫn giống nhau. Điều này đặc biệt có giá trị trong các dự án Kotlin Multiplatform Mobile (KMM) nơi mã dùng chung được sử dụng trên Android và iOS.

Cách hoạt động của tạo mã tại thời điểm biên dịch

Việc tạo mã trong kotlinx.serialization diễn ra theo ba giai đoạn. Ở giai đoạn đầu, trình biên dịch Kotlin phát hiện chú thích @Serializable trên một lớp và chuyển nó đến trình cắm Kotlin Symbol Processing (KSP). Ở giai đoạn thứ hai, KSP tạo ra một đối tượng trình tuần tự hóa triển khai giao diện KSerializer. Ở giai đoạn thứ ba, mã được tạo ra được biên dịch cùng với mã nguồn của dự án. Kết quả là không có giai đoạn nào được thực thi trong thời gian chạy ứng dụng.

Trình tuần tự hóa được tạo ra làm việc trực tiếp với các trường của lớp thông qua getter và setter, không cần phản xạ. Điều này có nghĩa là các trường có bộ điều chỉnh private cũng được tuần tự hóa nếu chúng được đánh dấu bằng @Serializable. Hiệu suất của cách tiếp cận này gần với tuần tự hóa thủ công: đối với các lớp đơn giản (5–10 trường), thời gian tuần tự hóa là 10–50 micro giây; đối với các đồ thị đối tượng phức tạp, lên tới 200 micro giây cho 1000 đối tượng.

Để thêm thư viện vào dự án Android hoặc Kotlin/JVM, bạn cần thêm trình cắm và phụ thuộc vào build.gradle.kts. Trình cắm org.jetbrains.kotlin.plugin.serialization với phiên bản khớp với phiên bản Kotlin sẽ kích hoạt việc tạo mã. Thư viện kotlinx-serialization-json được thêm vào phần dependencies với phiên bản độc lập với phiên bản Kotlin.

kotlin
// build.gradle.kts — thêm kotlinx.serialization
plugins {
    val kotlinVersion = "2.1.0"
    kotlin("jvm") version kotlinVersion
    kotlin("plugin.serialization") version kotlinVersion
}

dependencies {
    // Mô-đun tuần tự hóa chính
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

    // Các định dạng bổ sung
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}

Sử dụng cơ bản: tuần tự hóa JSON

JSON là định dạng phổ biến nhất trong kotlinx.serialization. Để tuần tự hóa một đối tượng, chỉ cần thêm chú thích @Serializable vào data class và gọi Json.encodeToString(). Để giải tuần tự hóa, gọi Json.decodeFromString() với chỉ định kiểu. Thư viện tự động xử lý các trường null, danh sách, đối tượng lồng nhau và enum. Tất cả các trường của lớp đều bắt buộc theo mặc định trừ khi có quy định khác.

Cấu hình JSON được thực hiện thông qua Json {} builder. Bạn có thể truyền ignoreUnknownKeys = true để bỏ qua các trường không xác định trong quá trình giải tuần tự hóa, prettyPrint = true để đầu ra định dạng, coerceInputValues = true để chuyển đổi các giá trị không hợp lệ thành giá trị mặc định. Cũng có sẵn encodeDefaults (tuần tự hóa các trường có giá trị mặc định) và classDiscriminator (tên trường cho tuần tự hóa đa hình).

kotlin
// Ví dụ tuần tự hóa và giải tuần tự hóa JSON
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

@Serializable
data class Project(
    val name: String,
    val stars: Int,
    val isActive: Boolean = true,
    val languages: List<String> = emptyList()
)

fun main() {
    val project = Project(
        name = "kotlinx.serialization",
        stars = 7200,
        languages = listOf("Kotlin", "Java")
    )

    // Tuần tự hóa JSON với prettyPrint
    val json = Json { prettyPrint = true }
    val jsonString = json.encodeToString(project)
    println(jsonString)
    /*
    {
        "name": "kotlinx.serialization",
        "stars": 7200,
        "isActive": true,
        "languages": ["Kotlin", "Java"]
    }
    */

    // Giải tuần tự hóa từ JSON
    val decoded = json.decodeFromString<Project>(jsonString)
    println(decoded.name)  // kotlinx.serialization
}

Ví dụ minh họa chu trình cơ bản của tuần tự hóa và giải tuần tự hóa. Data class Project với chú thích @Serializable tự động nhận được encodeToString và decodeFromString. Trường isActive có giá trị mặc định là true — nếu trường này thiếu trong JSON, giá trị mặc định sẽ được sử dụng. Nếu các trường không xác định xuất hiện trong JSON mà không có ignoreUnknownKeys = true, một SerializationException sẽ được ném ra.

Tuần tự hóa đa hình của sealed class

Sealed class là một trong những trường hợp sử dụng mạnh mẽ nhất của kotlinx.serialization. Thư viện hỗ trợ tuần tự hóa đa hình cho các phân cấp sealed class mà không cần cấu hình thêm: chỉ cần thêm chú thích @Serializable cho sealed class và tất cả các lớp con của nó. Trong quá trình tuần tự hóa, một trường “type” được thêm vào (có thể cấu hình qua classDiscriminator), trường này xác định kiểu cụ thể trong quá trình giải tuần tự hóa.

kotlin
// Tuần tự hóa đa hình của sealed class
@Serializable
sealed class Response

@Serializable
data class Success(val data: String) : Response()

@Serializable
data class Error(val code: Int, val message: String) : Response()

fun main() {
    val json = Json { classDiscriminator = "result_type" }

    val responses: List<Response> = listOf(
        Success(data = "Data loaded"),
        Error(code = 404, message = "Not found")
    )

    val jsonString = json.encodeToString(responses)
    println(jsonString)
    /*
    [
        {"result_type":"Success","data":"Data loaded"},
        {"result_type":"Error","code":404,"message":"Not found"}
    ]
    */

    val decoded = json.decodeFromString<List<Response>>(jsonString)
    when (val first = decoded[0]) {
        is Success -> println("Success: ${first.data}")
        is Error -> println("Error: ${first.code}")
    }
}

Tuần tự hóa đa hình của sealed class đặc biệt hữu ích trong các client API nơi máy chủ trả về các loại phản hồi khác nhau. Nếu không có kotlinx.serialization, bạn sẽ phải viết một trình giải tuần tự hóa thủ công với câu lệnh when trên trường phân biệt. Với thư viện, việc này được thực hiện bằng một chú thích duy nhất. classDiscriminator cho phép đổi tên trường đánh dấu (mặc định là “type”) thành bất kỳ giá trị nào mà máy chủ mong đợi.

Chú thích kotlinx.serialization: tổng quan đầy đủ

Thư viện cung cấp một tập hợp các chú thích để tinh chỉnh việc tuần tự hóa. Chính là @Serializable cho một lớp. Các chú thích bổ sung: @SerialName để đặt tên trường trong JSON (nếu khác với tên Kotlin), @Transient để loại trừ trường khỏi tuần tự hóa, @Required cho trường phải có mặt trong JSON, @EncodeDefault để buộc tuần tự hóa trường ngay cả khi có giá trị mặc định.

Chú thíchMục đíchVí dụ
@SerializableKích hoạt tạo trình tuần tự hóa cho một lớp@Serializable data class User
@SerialNameĐặt tên thay thế cho trường trong định dạng@SerialName(“user_name”) val name: String
@TransientLoại trừ trường khỏi tuần tự hóa@Transient val cache: MutableMap
@RequiredTrường bắt buộc trong JSON khi giải tuần tự hóa@Required val id: String
@EncodeDefaultTuần tự hóa trường ngay cả với giá trị mặc định@EncodeDefault val type: Type = Type.A
@SerializerLiên kết trình tuần tự hóa tùy chỉnh với một lớp@Serializer(forClass = Date::class)

Chú thích @SerialName rất quan trọng khi làm việc với các API nơi tên trường ở dạng snake_case, trong khi kiểu Kotlin là camelCase. Ví dụ, máy chủ gửi “user_id”, nhưng mã Kotlin sử dụng userId. @SerialName(“user_id”) giải quyết vấn đề này mà không cần các bộ ánh xạ bổ sung. @Transient hữu ích cho các trường không nên gửi lên máy chủ — ví dụ, các giá trị tạm thời hoặc bộ nhớ đệm.

@Required như một thay thế cho các trường nullable

Theo mặc định, tất cả các trường trong kotlinx.serialization đều bắt buộc. Nếu một trường có thể vắng mặt trong JSON, bạn cần làm cho nó có thể nullable (String?) hoặc đặt giá trị mặc định (val name: String = “”). Tuy nhiên, có những tình huống mà một trường không nullable trong Kotlin nhưng có thể thiếu trong JSON do việc thay đổi phiên bản API. Trong trường hợp này, @Required ném ra SerializationException khi trường vắng mặt, trong khi giá trị mặc định sẽ điền vào mà không có lỗi.

Trình tuần tự hóa tùy chỉnh: KSerializer và điều khiển thủ công

KSerializer là giao diện mà tất cả các trình tuần tự hóa trong kotlinx.serialization triển khai. Nếu việc tạo mã tiêu chuẩn không phù hợp (ví dụ, để làm việc với Date, Bitmap hoặc một định dạng nhị phân cụ thể), bạn có thể viết trình tuần tự hóa của riêng mình. Để làm điều này, hãy triển khai các phương thức serialize() và deserialize(), và cung cấp một bộ mô tả — mô tả cấu trúc cho lược đồ định dạng.

Các trình tuần tự hóa tùy chỉnh được kết nối theo hai cách: thông qua chú thích @Serializable(with = MySerializer::class) để liên kết với một lớp cụ thể, hoặc toàn cục qua Json { serializersModule = ... } để liên kết với tất cả các thể hiện của một kiểu. Cách thứ hai được ưa tiên cho các kiểu tích hợp sẵn (Date, UUID) để tránh phải viết chú thích trên mỗi trường.

kotlin
// Trình tuần tự hóa tùy chỉnh cho java.util.Date
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

object DateSerializer : KSerializer<Date> {
    private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)

    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Date) {
        encoder.encodeString(dateFormat.format(value))
    }

    override fun deserialize(decoder: Decoder): Date {
        return dateFormat.parse(decoder.decodeString())
    }
}

// Sử dụng trình tuần tự hóa tùy chỉnh
@Serializable
data class Event(
    val title: String,
    @Serializable(with = DateSerializer::class)
    val date: Date
)

fun main() {
    val json = Json { prettyPrint = true }
    val event = Event("Release", Date())
    println(json.encodeToString(event))
}

Trong ví dụ, DateSerializer chuyển đổi java.util.Date thành chuỗi ISO 8601. Nếu không có trình tuần tự hóa tùy chỉnh, kotlinx.serialization không thể làm việc với Date — đây là kiểu không có trong thư viện tiêu chuẩn Kotlin. @Serializable(with = DateSerializer::class) trên một trường cụ thể chỉ liên kết trình tuần tự hóa cho trường đó. Để đăng ký toàn cục tất cả các Date, hãy sử dụng Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.

Định dạng tuần tự hóa: JSON, ProtoBuf, CBOR, HOCON

kotlinx.serialization không chỉ giới hạn ở JSON. Thư viện hỗ trợ bốn định dạng tích hợp sẵn, mỗi định dạng có mô-đun và cấu hình riêng. JSON (kotlinx-serialization-json) phổ quát, có thể đọc được bởi con người, phù hợp cho REST API. ProtoBuf (kotlinx-serialization-protobuf) là nhị phân, nhỏ gọn, có lược đồ bắt buộc, cho các vi dịch vụ tải cao. CBOR (kotlinx-serialization-cbor) là một dạng nhị phân tương đương JSON, thuận tiện cho IoT và các thiết bị di động có băng thông hạn chế. HOCON (kotlinx-serialization-hocon) là định dạng cấu hình tương thích với TypeSafe Config.

Định dạngMô-đunLoạiLược đồSử dụng điển hình
JSONkotlinx-serialization-jsonVăn bảnTùy chọnREST API, lưu trữ dữ liệu
ProtoBufkotlinx-serialization-protobufNhị phânBắt buộc (.proto)Vi dịch vụ, gRPC
CBORkotlinx-serialization-cborNhị phânTùy chọnIoT, thiết bị di động
HOCONkotlinx-serialization-hoconVăn bảnTùy chọnTệp cấu hình

ProtoBuf yêu cầu xác định lược đồ trong các tệp .proto, nhưng kotlinx-serialization-protobuf tạo các lớp Kotlin trực tiếp từ @Serializable mà không cần .proto. Điều này đơn giản hóa việc phát triển: chỉ cần thêm chú thích cho data class và sử dụng ProtoBuf.encodeToByteArray(). CBOR đặc biệt phù hợp cho Android khi cần truyền dữ liệu nhị phân nhỏ gọn qua NFC hoặc BLE. Các thông điệp CBOR trung bình nhỏ hơn 20–30% so với JSON cho cùng một tập dữ liệu.

Chọn định dạng cho dự án

Đối với REST API trong ứng dụng di động, JSON là lựa chọn tốt nhất — có thể gỡ lỗi mà không cần công cụ bổ sung, có thể đọc được trong nhật ký và tương thích với mọi phía back-end. Nếu ứng dụng truyền khối lượng dữ liệu lớn giữa các vi dịch vụ (hàng trăm megabyte), ProtoBuf cung cấp lợi thế về tốc độ lên đến 5x nhờ mã hóa nhị phân. Để lưu trữ cài đặt trong tệp, hãy sử dụng HOCON hoặc JSON. Đối với các thiết bị có giới hạn băng thông nghiêm ngặt (cảm biến IoT), hãy sử dụng CBOR.

Lỗi thường gặp khi làm việc với kotlinx.serialization

Lỗi đầu tiên là bỏ qua các khóa không xác định trong quá trình giải tuần tự hóa. Nếu máy chủ thêm một trường mới và bạn để ignoreUnknownKeys = false, ứng dụng sẽ gặp sự cố với SerializationException. Cờ này bị tắt theo mặc định. Giải pháp: Luôn đặt Json { ignoreUnknownKeys = true } cho mã sản xuất để chống chịu được với các thay đổi API.

Lỗi thứ hai là tuần tự hóa các trường internal hoặc private trong một data class. Trong một data class Kotlin, tất cả các trường trong hàm tạo chính đều được tuần tự hóa theo mặc định. Nếu một trường chứa dữ liệu nhạy cảm (mật khẩu, mã thông báo), nó phải được đánh dấu bằng @Transient hoặc được đưa ra khỏi hàm tạo chính. @Transient loại trừ trường khỏi JSON hoàn toàn, nhưng bên trong hàm tạo nó có thể gây ra lỗi — tốt hơn nên xác định các trường này trong phần thân lớp với @Transient.

Lỗi thứ ba là tuần tự hóa đa hình mà không có sealed class. Nếu bạn sử dụng open class thay vì sealed, kotlinx.serialization yêu cầu đăng ký rõ ràng tất cả các lớp con trong serializersModule. Không giống như sealed class, nơi trình biên dịch biết tất cả các lớp con, open class cho phép mở rộng tùy ý — thư viện không thể tự động xác định tất cả các kiểu con. Việc đăng ký được thực hiện qua Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.

Lỗi phiên bản thư viện

Phiên bản kotlinx.serialization phải tương thích với phiên bản Kotlin. JetBrains công bố bảng tương thích: kotlinx-serialization 1.6.x tương thích với Kotlin 1.9.x, 1.7.x với Kotlin 2.0.x và 2.1.x. Sự không khớp phiên bản gây ra các lỗi biên dịch khó hiểu như “Symbol ‘serializer’ is missing”. Luôn kiểm tra phiên bản mới nhất trên Maven Central hoặc trong kho lưu trữ GitHub của dự án.

Câu hỏi thường gặp

kotlinx.serialization khác gì so với Gson và Moshi?

kotlinx.serialization sử dụng tạo mã tại thời điểm biên dịch qua KSP, trong khi Gson và Moshi sử dụng phản xạ trong thời gian chạy. Điều này mang lại lợi thế về hiệu suất (nhanh hơn Gson 3–5 lần) và an toàn kiểu. Gson tuần tự hóa mọi trường mà không cần chú thích, có thể dẫn đến rò rỉ dữ liệu. kotlinx.serialization yêu cầu chú thích tường minh @Serializable, an toàn hơn. Moshi cũng hỗ trợ codegen, nhưng chỉ cho JVM và Android.

kotlinx.serialization có hỗ trợ Kotlin Multiplatform không?

, kotlinx.serialization là thư viện đa nền tảng chính thức của JetBrains. Nó hoạt động trên Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) và Kotlin/Wasm. API được thống nhất trên tất cả các nền tảng: @Serializable + Json.encodeToString() hoạt động giống nhau ở mọi nơi. Đối với iOS, không cần thiết lập thêm — Kotlin/Native biên dịch mã đã tuần tự hóa thành tệp nhị phân gốc.

Các trường null được xử lý trong JSON như thế nào?

Các trường nullable (String?) được giải tuần tự hóa là null nếu giá trị thiếu hoặc là null trong JSON. Đối với các trường không nullable (String) không có giá trị mặc định, việc thiếu trường trong JSON sẽ ném ra SerializationException. Nếu bạn muốn các giá trị null không xuất hiện trong JSON, hãy cấu hình Json { encodeDefaults = false }. Điều này loại trừ tất cả các trường bằng giá trị mặc định (bao gồm null cho các kiểu nullable).

Làm gì nếu máy chủ gửi các trường snake_case?

Sử dụng @SerialName(“ten_snake_case”) trên mỗi trường có tên khác với định dạng Kotlin. Thay vào đó, đối với Kotlin 2.0+, Json { namingStrategy = JsonNamingStrategy.SnakeCase } có sẵn để chuyển đổi tự động camelCase ↔ snake_case. Cài đặt này áp dụng cho tất cả các trường cùng một lúc. Nếu cần tùy chỉnh một phần, hãy kết hợp @SerialName với chiến lược toàn cục.

Có thể tuần tự hóa Kotlin Flow hoặc coroutine không?

Không, Flow và coroutine không thể tuần tự hóa trực tiếp — chúng đại diện cho việc thực thi không đồng bộ, không phải dữ liệu. Để truyền dữ liệu từ Flow, hãy thu thập nó vào một bộ sưu tập qua .toList() trong một coroutine và tuần tự hóa bộ sưu tập đó. Tương tự, bạn không thể tuần tự hóa Job, Deferred hoặc Continuation. Chỉ tuần tự hóa các data class — các mô hình dữ liệu không có logic hành vi.

Tổng kết

  • kotlinx.serialization — tuần tự hóa tại thời điểm biên dịch qua @Serializable, không cần phản xạ, nhanh hơn Gson đến 5 lần
  • @Serializable, @SerialName, @Transient — các chú thích chính để cấu hình tuần tự hóa trường và lớp
  • Json {} builder cấu hình JSON: ignoreUnknownKeys, prettyPrint, coerceInputValues, encodeDefaults
  • Sealed class và tuần tự hóa đa hình — hỗ trợ liền mạch các phân cấp kiểu mà không cần mã bổ sung
  • KSerializer — giao diện cho các trình tuần tự hóa tùy chỉnh của các kiểu không chuẩn (Date, Bitmap, UUID)
  • Bốn định dạng: JSON, ProtoBuf, CBOR, HOCON — được thêm dưới dạng mô-đun, API thống nhất cho tất cả
  • Đa nền tảng — một mã nguồn cho JVM, Native, JS và Wasm; rất quan trọng cho KMM và các mô-đun dùng chung

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm