kotlinx.serialization — pustaka multiplatform dari JetBrains untuk mengonversi objek Kotlin ke JSON, ProtoBuf, CBOR dan format lainnya tanpa menggunakan refleksi. Berbeda dengan Gson dan Moshi, ia menghasilkan kode serialisator pada tahap kompilasi melalui anotasi @Serializable, yang memberikan kinerja tinggi dan keamanan tipe. Menurut GitHub Kotlin/kotlinx.serialization, pustaka ini mendukung Kotlin/JVM, Kotlin/Native, Kotlin/JS dan Kotlin/Wasm.
Poin Utama
kotlinx.serialization — adalah pustaka serialisasi bawaan untuk Kotlin, yang dikembangkan oleh JetBrains sebagai bagian dari ekosistem resmi Kotlin. Perbedaan utamanya dari solusi pihak ketiga (Gson, Moshi, Jackson) adalah tidak menggunakan refleksi saat runtime. Sebagai gantinya, kode serialisator dihasilkan pada tahap kompilasi menggunakan Kotlin Symbol Processing (KSP) atau Plugin Kompiler Kotlin. Ini memberikan peningkatan kinerja hingga 3-5 kali dibandingkan dengan Gson dan menjamin keamanan tipe.
Pustaka ini secara resmi mendukung empat format: JSON (melalui modul kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) dan HOCON (kotlinx-serialization-hocon). Format ditambahkan sebagai dependensi terpisah di build.gradle.kts, yang memungkinkan untuk tidak menarik pustaka yang tidak perlu ke dalam proyek. Untuk setiap format, terdapat set parameter konfigurasi sendiri.
Multiplatform — fitur utama pustaka. Kelas yang sama dengan @Serializable bekerja di semua platform target: JVM (Android, Backend), Native (iOS), JS (Web, React) dan Wasm (WebAssembly). Pengembang tidak perlu menulis implementasi serialisasi yang berbeda untuk setiap platform — kode tetap seragam. Ini sangat berharga dalam proyek Kotlin Multiplatform Mobile (KMM), di mana kode bersama dibagi antara Android dan iOS.
Pembuatan kode di kotlinx.serialization terjadi dalam tiga tahap. Pada tahap pertama, kompiler Kotlin mendeteksi anotasi @Serializable pada kelas dan meneruskannya ke plugin Kotlin Symbol Processing (KSP). Pada tahap kedua, KSP menghasilkan objek serialisator yang mengimplementasikan antarmuka KSerializer. Pada tahap ketiga, kode yang dihasilkan dikompilasi bersama dengan kode sumber proyek. Hasilnya, tidak satu pun dari tahap ini yang dijalankan selama runtime aplikasi.
Serialisator yang dihasilkan bekerja langsung dengan bidang kelas melalui getter dan setter mereka, tanpa refleksi. Ini berarti bahwa bidang dengan pengubah private juga diserialisasi jika ditandai dengan @Serializable. Kinerja pendekatan ini mendekati serialisasi manual: untuk kelas sederhana (5-10 bidang) waktu serialisasi adalah 10-50 mikrodetik, untuk graf objek yang kompleks — hingga 200 mikrodetik per 1000 objek.
Untuk menghubungkan pustaka dalam proyek Android atau Kotlin/JVM, Anda perlu menambahkan plugin dan dependensi di build.gradle.kts. Plugin org.jetbrains.kotlin.plugin.serialization dalam versi yang sesuai dengan versi Kotlin mengaktifkan pembuatan kode. Pustaka kotlinx-serialization-json ditambahkan di bagian dependencies dengan versi yang tidak tergantung pada versi Kotlin.
// build.gradle.kts — menghubungkan kotlinx.serialization
plugins {
val kotlinVersion = "2.1.0"
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
dependencies {
// Modul serialisasi utama
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
// Format tambahan
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}
JSON — format paling populer di kotlinx.serialization. Untuk menserialisasi objek, cukup tempatkan anotasi @Serializable pada data class dan panggil Json.encodeToString(). Untuk deserialisasi — Json.decodeFromString() dengan menentukan tipe. Pustaka secara otomatis menangani bidang null, daftar, objek bersarang, dan enum. Semua bidang kelas secara default wajib, kecuali ditentukan lain.
Konfigurasi JSON dilakukan melalui Json {} builder. Di konstruktor, Anda dapat memberikan ignoreUnknownKeys = true untuk melewati bidang yang tidak dikenal saat deserialisasi, prettyPrint = true untuk keluaran terformat, coerceInputValues = true untuk mengonversi nilai yang salah menjadi nilai default. Juga tersedia pengaturan encodeDefaults (serialisasi bidang dengan nilai default) dan classDiscriminator (nama bidang untuk serialisasi polimorfik).
// Contoh serialisasi dan deserialisasi 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")
)
// Serialisasi ke JSON dengan prettyPrint
val json = Json { prettyPrint = true }
val jsonString = json.encodeToString(project)
println(jsonString)
/*
{
"name": "kotlinx.serialization",
"stars": 7200,
"isActive": true,
"languages": ["Kotlin", "Java"]
}
*/
// Deserialisasi dari JSON
val decoded = json.decodeFromString<Project>(jsonString)
println(decoded.name) // kotlinx.serialization
}
Contoh menunjukkan siklus dasar serialisasi dan deserialisasi. Data class Project dengan anotasi @Serializable secara otomatis mendapatkan encodeToString dan decodeFromString. Bidang isActive memiliki nilai default true — jika bidang ini tidak ada di JSON, nilai default digunakan. Jika bidang yang tidak dikenal muncul di JSON tanpa ignoreUnknownKeys = true, pengecualian SerializationException akan dilemparkan.
Sealed class — salah satu kasus penggunaan kotlinx.serialization yang paling kuat. Pustaka mendukung serialisasi polimorfik untuk hierarki sealed class tanpa konfigurasi tambahan: cukup tandai sealed class dan semua turunannya dengan @Serializable. Saat serialisasi, bidang „type” (dapat dikonfigurasi melalui classDiscriminator) ditambahkan, yang menjadi dasar penentuan tipe konkret saat deserialisasi.
// Serialisasi polimorfik 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}")
}
}
Serialisasi polimorfik sealed class sangat berguna di klien API, di mana server mengembalikan berbagai jenis respons. Tanpa kotlinx.serialization, Anda harus menulis deserialisator manual dengan when berdasarkan bidang diskriminator. Dengan pustaka, ini dilakukan dengan satu anotasi. classDiscriminator memungkinkan mengganti nama bidang penanda (default „type”) ke nilai apa pun yang diharapkan server.
Pustaka menyediakan seperangkat anotasi untuk penyesuaian serialisasi yang halus. Yang utama adalah @Serializable untuk kelas. Anotasi tambahan: @SerialName untuk menentukan nama bidang di JSON (jika berbeda dari nama Kotlin), @Transient untuk mengecualikan bidang dari serialisasi, @Required untuk bidang yang harus ada di JSON, @EncodeDefault untuk serialisasi paksa bidang dengan nilai default.
| Anotasi | Tujuan | Contoh |
|---|---|---|
| @Serializable | Mengaktifkan pembuatan serialisator untuk kelas | @Serializable data class User |
| @SerialName | Menentukan nama alternatif bidang dalam format | @SerialName(“user_name”) val name: String |
| @Transient | Mengecualikan bidang dari serialisasi | @Transient val cache: MutableMap |
| @Required | Bidang wajib di JSON saat deserialisasi | @Required val id: String |
| @EncodeDefault | Menserialisasi bidang bahkan dengan nilai default | @EncodeDefault val type: Type = Type.A |
| @Serializer | Menghubungkan serialisator kustom ke kelas | @Serializer(forClass = Date::class) |
Anotasi @SerialName sangat penting saat bekerja dengan API di mana nama bidang dalam snake_case dan gaya Kotlin adalah camelCase. Misalnya, server mengirim “user_id” dan dalam kode Kotlin digunakan userId. @SerialName(“user_id”) memecahkan masalah ini tanpa mapper tambahan. @Transient berguna untuk bidang yang tidak perlu dikirim ke server — misalnya, nilai komputasi sementara atau cache.
Secara default semua bidang di kotlinx.serialization wajib. Jika bidang mungkin tidak ada di JSON, Anda harus membuatnya nullable (String?) atau menetapkan nilai default (val name: String = “”). Namun, ada situasi ketika bidang tidak nullable di Kotlin tetapi mungkin tidak ada di JSON karena pembuatan versi API. Dalam hal ini, @Required melempar SerializationException saat bidang tidak ada, sementara nilai default mengisi default tanpa kesalahan.
KSerializer — antarmuka yang diimplementasikan oleh semua serialisator di kotlinx.serialization. Jika pembuatan kode standar tidak sesuai (misalnya, untuk bekerja dengan Date, Bitmap, atau format biner spesifik), Anda dapat menulis serialisator sendiri. Untuk ini, Anda perlu mengimplementasikan metode serialize() dan deserialize(), serta menyediakan descriptor — deskripsi struktur untuk skema format.
Serialisator kustom dihubungkan dengan dua cara: melalui anotasi @Serializable(with = MySerializer::class) untuk menghubungkan ke kelas tertentu atau secara global melalui Json { serializersModule = ... } untuk menghubungkan ke semua instance tipe. Cara kedua lebih disukai untuk tipe bawaan (Date, UUID) agar tidak perlu menulis anotasi pada setiap bidang.
// Serialisator kustom untuk 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())
}
}
// Penggunaan serialisator kustom
@Serializable
data class Event(
val title: String,
@Serializable(with = DateSerializer::class)
val date: Date
)
fun main() {
val json = Json { prettyPrint = true }
val event = Event("Rilis", Date())
println(json.encodeToString(event))
}
Dalam contoh, DateSerializer mengonversi java.util.Date menjadi string ISO 8601. Tanpa serialisator kustom, kotlinx.serialization tidak dapat bekerja dengan Date — ini adalah tipe yang bukan bagian dari pustaka standar Kotlin. @Serializable(with = DateSerializer::class) pada bidang tertentu menghubungkan serialisator hanya untuk bidang itu. Untuk pendaftaran global semua Date, gunakan Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.
kotlinx.serialization tidak terbatas pada JSON. Pustaka mendukung empat format bawaan, masing-masing dengan modul dan konfigurasinya sendiri. JSON (kotlinx-serialization-json) — universal, dapat dibaca manusia, cocok untuk REST API. ProtoBuf (kotlinx-serialization-protobuf) — biner, kompak, dengan skema wajib, untuk mikrolayanan dengan beban tinggi. CBOR (kotlinx-serialization-cbor) — padanan biner JSON, nyaman untuk IoT dan perangkat seluler dengan lalu lintas terbatas. HOCON (kotlinx-serialization-hocon) — format konfigurasi, kompatibel dengan TypeSafe Config.
| Format | Modul | Tipe | Skema | Penggunaan Umum |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | Teks | Opsional | REST API, penyimpanan data |
| ProtoBuf | kotlinx-serialization-protobuf | Biner | Wajib (.proto) | Mikrolayanan, gRPC |
| CBOR | kotlinx-serialization-cbor | Biner | Opsional | IoT, perangkat seluler |
| HOCON | kotlinx-serialization-hocon | Teks | Opsional | File konfigurasi |
ProtoBuf memerlukan definisi skema dalam file .proto, tetapi kotlinx-serialization-protobuf menghasilkan kelas Kotlin langsung dari @Serializable tanpa .proto. Ini menyederhanakan pengembangan: cukup anotasi data class dan gunakan ProtoBuf.encodeToByteArray(). CBOR sangat relevan untuk framework Android ketika perlu mengirim data biner kompak melalui NFC atau BLE. Ukuran pesan CBOR rata-rata 20-30% lebih kecil dari JSON untuk kumpulan data yang sama.
Untuk REST API di aplikasi seluler, JSON optimal — dapat di-debug tanpa alat tambahan, terbaca di log, dan kompatibel dengan backend apa pun. Jika aplikasi mentransfer volume data besar antar mikrolayanan (ratusan megabita) — ProtoBuf akan memberikan peningkatan kecepatan hingga 5 kali berkat pengkodean biner. Untuk menyimpan pengaturan di file, gunakan HOCON atau JSON. Untuk perangkat dengan batasan lalu lintas ketat (sensor IoT) — CBOR.
Kesalahan pertama — mengabaikan kunci yang tidak dikenal saat deserialisasi. Jika server menambahkan bidang baru dan Anda memiliki ignoreUnknownKeys = false, aplikasi akan crash dengan SerializationException. Secara default, bendera ini dimatikan. Solusi: selalu atur Json { ignoreUnknownKeys = true } untuk kode produksi agar tahan terhadap perubahan API.
Kesalahan kedua — serialisasi bidang internal atau private di data class. Di Kotlin data class, semua bidang di primary constructor secara default diserialisasi. Jika bidang berisi data sensitif (kata sandi, token), Anda harus menandainya dengan @Transient atau mengeluarkannya dari primary constructor. @Transient mengecualikan bidang sepenuhnya dari JSON, tetapi di constructor dapat menyebabkan kesalahan — lebih baik mendefinisikan bidang seperti itu di body kelas dengan @Transient.
Kesalahan ketiga — serialisasi polimorfik tanpa sealed class. Jika Anda menggunakan open class alih-alih sealed, kotlinx.serialization memerlukan pendaftaran eksplisit semua turunan di serializersModule. Tidak seperti sealed class, di mana kompiler mengetahui semua turunan, open class memungkinkan ekstensi arbitrer — pustaka tidak dapat secara otomatis menentukan semua subtipe. Pendaftaran dilakukan melalui Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.
Versi kotlinx.serialization harus kompatibel dengan versi Kotlin. JetBrains menerbitkan tabel kompatibilitas: kotlinx-serialization 1.6.x kompatibel dengan Kotlin 1.9.x, 1.7.x — dengan Kotlin 2.0.x dan 2.1.x. Ketidakcocokan versi menyebabkan kesalahan kompilasi yang membingungkan seperti „Symbol ‘serializer’ is missing”. Selalu periksa versi saat ini di mavenCentral atau repositori GitHub proyek.
Pertanyaan yang Sering Diajukan
kotlinx.serialization menggunakan pembuatan kode waktu kompilasi melalui KSP, sementara Gson dan Moshi menggunakan refleksi runtime. Ini memberikan keunggulan dalam kinerja (3-5 kali lebih cepat dari Gson) dan keamanan tipe. Gson menserialisasi bidang apa pun tanpa anotasi, yang dapat menyebabkan kebocoran data. kotlinx.serialization memerlukan anotasi @Serializable eksplisit, yang lebih aman. Moshi juga mendukung codegen, tetapi hanya untuk JVM dan Android.
Ya, kotlinx.serialization adalah pustaka multiplatform resmi JetBrains. Ia bekerja di Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) dan Kotlin/Wasm. API seragam untuk semua platform: @Serializable + Json.encodeToString() bekerja sama di mana-mana. Untuk iOS tidak diperlukan pengaturan tambahan — Kotlin/Native mengompilasi kode yang diserialisasi menjadi biner asli.
Bidang nullable (String?) dideserialisasi sebagai null jika nilai di JSON tidak ada atau ditentukan sebagai null. Untuk bidang non-nullable (String) tanpa nilai default, ketiadaan bidang di JSON akan menyebabkan SerializationException. Jika Anda ingin nilai null tidak masuk ke JSON, konfigurasikan Json { encodeDefaults = false }. Ini akan mengecualikan dari keluaran semua bidang yang sama dengan default (termasuk null untuk nullable).
Gunakan @SerialName(“snake_case_name”) pada setiap bidang yang namanya berbeda dari format Kotlin. Alternatifnya, untuk Kotlin 2.0+ tersedia Json { namingStrategy = JsonNamingStrategy.SnakeCase } — konversi otomatis camelCase ↔ snake_case. Pengaturan ini berlaku untuk semua bidang sekaligus. Jika diperlukan kustomisasi parsial, gabungkan @SerialName dengan strategi global.
Tidak, Flow dan coroutine tidak dapat diserialisasi secara langsung — mereka mewakili eksekusi asinkron, bukan data. Untuk mentransfer data dari Flow, Anda harus mengumpulkannya ke dalam koleksi melalui .toList() di coroutine dan menserialisasi koleksi tersebut. Demikian pula, Job, Deferred atau Continuation tidak dapat diserialisasi. Serialisasi hanya data class — model data tanpa logika perilaku.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga