kotlinx.serialization — کتابخانه چندسکویی از JetBrains برای تبدیل اشیاء Kotlin به JSON، ProtoBuf، CBOR و فرمتهای دیگر بدون استفاده از بازتاب. برخلاف Gson و Moshi، کد سریالساز را در مرحله کامپایل از طریق حاشیهنویسی @Serializable تولید میکند که عملکرد بالا و امنیت نوع را فراهم میکند. به گفته GitHub Kotlin/kotlinx.serialization، کتابخانه از Kotlin/JVM، Kotlin/Native، Kotlin/JS و Kotlin/Wasm پشتیبانی میکند.
نکات اصلی
kotlinx.serialization — کتابخانه سریالسازی داخلی برای Kotlin است که توسط JetBrains به عنوان بخشی از اکوسیستم رسمی Kotlin توسعه یافته است. تفاوت اصلی آن با راهحلهای شخص ثالث (Gson، Moshi، Jackson) این است که در زمان اجرا از بازتاب استفاده نمیکند. در عوض، کد سریالساز در مرحله کامپایل با استفاده از Kotlin Symbol Processing (KSP) یا افزونه کامپایلر Kotlin تولید میشود. این باعث افزایش عملکرد تا 3-5 برابر در مقایسه با Gson و تضمین امنیت نوع میشود.
کتابخانه به طور رسمی از چهار فرمت پشتیبانی میکند: JSON (از طریق ماژول kotlinx-serialization-json)، ProtoBuf (kotlinx-serialization-protobuf)، CBOR (kotlinx-serialization-cbor) و HOCON (kotlinx-serialization-hocon). فرمتها به عنوان وابستگیهای جداگانه در build.gradle.kts اضافه میشوند که امکان عدم اضافه کردن کتابخانههای غیرضروری به پروژه را فراهم میکند. برای هر فرمت مجموعه پارامترهای پیکربندی خاص خود وجود دارد.
چندسکویی — ویژگی کلیدی کتابخانه. همان کلاس با @Serializable روی همه پلتفرمهای هدف کار میکند: JVM (Android، Backend)، Native (iOS)، JS (Web، React) و Wasm (WebAssembly). توسعهدهنده نیازی به نوشتن پیادهسازیهای مختلف سریالسازی برای هر پلتفرم ندارد — کد یکسان باقی میماند. این به ویژه در پروژههای Kotlin Multiplatform Mobile (KMM) که کد مشترک بین Android و iOS به اشتراک گذاشته میشود، ارزشمند است.
تولید کد در kotlinx.serialization در سه مرحله انجام میشود. در مرحله اول، کامپایلر Kotlin حاشیهنویسی @Serializable را روی کلاس تشخیص داده و آن را به افزونه Kotlin Symbol Processing (KSP) ارسال میکند. در مرحله دوم، KSP یک شیء سریالساز را تولید میکند که رابط KSerializer را پیادهسازی میکند. در مرحله سوم، کد تولید شده همراه با کد منبع پروژه کامپایل میشود. در نتیجه، هیچیک از این مراحل در زمان اجرای برنامه انجام نمیشود.
سریالساز تولید شده مستقیماً با فیلدهای کلاس از طریق getter و setterهای آنها، بدون بازتاب کار میکند. این بدان معناست که فیلدهای با اصلاحکننده private نیز اگر با @Serializable مشخص شده باشند، سریالسازی میشوند. عملکرد این رویکرد نزدیک به سریالسازی دستی است: برای کلاسهای ساده (5-10 فیلد) زمان سریالسازی 10-50 میکروثانیه، برای گرافهای پیچیده اشیاء — تا 200 میکروثانیه برای 1000 شیء.
برای اتصال کتابخانه در پروژه Android یا Kotlin/JVM باید افزونه و وابستگیها را در build.gradle.kts اضافه کنید. افزونه org.jetbrains.kotlin.plugin.serialization با نسخهای مطابق با نسخه Kotlin، تولید کد را فعال میکند. کتابخانه kotlinx-serialization-json در بخش dependencies با نسخهای مستقل از نسخه Kotlin اضافه میشود.
// build.gradle.kts — اتصال kotlinx.serialization
plugins {
val kotlinVersion = "2.1.0"
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
dependencies {
// ماژول اصلی سریالسازی
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
// فرمتهای اضافی
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}
JSON — محبوبترین فرمت در kotlinx.serialization. برای سریالسازی یک شیء کافی است حاشیهنویسی @Serializable را روی data class قرار دهید و Json.encodeToString() را فراخوانی کنید. برای دسریالسازی — Json.decodeFromString() با مشخص کردن نوع. کتابخانه به طور خودکار فیلدهای null، لیستها، اشیاء تو در تو و enumها را مدیریت میکند. همه فیلدهای کلاس به طور پیشفرض الزامی هستند، مگر اینکه طور دیگری مشخص شده باشد.
پیکربندی JSON از طریق Json {} builder انجام میشود. در سازنده میتوان ignoreUnknownKeys = true برای نادیده گرفتن فیلدهای ناشناخته هنگام دسریالسازی، prettyPrint = true برای خروجی قالببندی شده، coerceInputValues = true برای تبدیل مقادیر نادرست به مقادیر پیشفرض را منتقل کرد. همچنین تنظیمات encodeDefaults (سریالسازی فیلدهای با مقادیر پیشفرض) و classDiscriminator (نام فیلد برای سریالسازی چندریختی) در دسترس هستند.
// مثال سریالسازی و دسریالسازی 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")
)
// سریالسازی به JSON با prettyPrint
val json = Json { prettyPrint = true }
val jsonString = json.encodeToString(project)
println(jsonString)
/*
{
"name": "kotlinx.serialization",
"stars": 7200,
"isActive": true,
"languages": ["Kotlin", "Java"]
}
*/
// دسریالسازی از JSON
val decoded = json.decodeFromString<Project>(jsonString)
println(decoded.name) // kotlinx.serialization
}
مثال چرخه پایه سریالسازی و دسریالسازی را نشان میدهد. Data class Project با حاشیهنویسی @Serializable به طور خودکار encodeToString و decodeFromString را دریافت میکند. فیلد isActive مقدار پیشفرض true دارد — اگر این فیلد در JSON وجود نداشته باشد، از مقدار پیشفرض استفاده میشود. اگر فیلدهای ناشناخته بدون ignoreUnknownKeys = true به JSON بیایند، استثنای SerializationException پرتاب میشود.
Sealed class — یکی از قدرتمندترین موارد استفاده kotlinx.serialization. کتابخانه از سریالسازی چندریختی برای سلسلهمراتب sealed class بدون پیکربندی اضافی پشتیبانی میکند: کافی است sealed class و همه وراثهای آن را با @Serializable مشخص کنید. هنگام سریالسازی فیلد «type» (قابل تنظیم از طریق classDiscriminator) اضافه میشود که بر اساس آن هنگام دسریالسازی نوع مشخص تعیین میشود.
// سریالسازی چندریختی 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}")
}
}
سریالسازی چندریختی sealed class به ویژه در کلاینتهای API مفید است، جایی که سرور انواع مختلف پاسخ را برمیگرداند. بدون kotlinx.serialization باید یک دسریالساز دستی با when بر اساس فیلد تشخیصدهنده مینوشتید. با کتابخانه این کار با یک حاشیهنویسی انجام میشود. classDiscriminator اجازه تغییر نام فیلد نشانگر (پیشفرض «type») به هر مقدار مورد انتظار سرور را میدهد.
کتابخانه مجموعهای از حاشیهنویسیها برای تنظیم دقیق سریالسازی ارائه میدهد. اصلیترین آنها @Serializable برای کلاس است. حاشیهنویسیهای اضافی: @SerialName برای تعیین نام فیلد در JSON (اگر با نام Kotlin متفاوت است)، @Transient برای حذف فیلد از سریالسازی، @Required برای فیلدی که باید در JSON وجود داشته باشد، @EncodeDefault برای سریالسازی اجباری فیلدی با مقدار پیشفرض.
| حاشیهنویسی | هدف | مثال |
|---|---|---|
| @Serializable | تولید سریالساز را برای کلاس فعال میکند | @Serializable data class User |
| @SerialName | نام جایگزین فیلد را در فرمت تعیین میکند | @SerialName("user_name") val name: String |
| @Transient | فیلد را از سریالسازی حذف میکند | @Transient val cache: MutableMap |
| @Required | فیلد در JSON هنگام دسریالسازی الزامی است | @Required val id: String |
| @EncodeDefault | فیلد را حتی با مقدار پیشفرض سریالسازی میکند | @EncodeDefault val type: Type = Type.A |
| @Serializer | سریالساز سفارشی را به کلاس متصل میکند | @Serializer(forClass = Date::class) |
حاشیهنویسی @SerialName هنگام کار با APIهایی که نام فیلدها به صورت snake_case و سبک Kotlin camelCase است، حیاتی است. مثلاً سرور "user_id" میفرستد و در کد Kotlin از userId استفاده میشود. @SerialName("user_id") این مشکل را بدون مپرهای اضافی حل میکند. @Transient برای فیلدهایی که نیازی به ارسال به سرور ندارند مفید است — مثلاً مقادیر محاسباتی موقت یا حافظه نهان.
به طور پیشفرض همه فیلدها در kotlinx.serialization الزامی هستند. اگر فیلدی ممکن است در JSON وجود نداشته باشد، باید آن را nullable (String?) کنید یا مقدار پیشفرض تعیین کنید (val name: String = ""). با این حال، مواقعی وجود دارد که فیلد در Kotlin nullable نیست، اما به دلیل نسخهبندی API ممکن است در JSON نباشد. در این حالت @Required هنگام عدم وجود فیلد SerializationException پرتاب میکند و مقدار پیشفرض default را بدون خطا پر میکند.
KSerializer — رابطی است که همه سریالسازها در kotlinx.serialization پیادهسازی میکنند. اگر تولید کد استاندارد مناسب نیست (مثلاً برای کار با Date، Bitmap یا فرمت باینری خاص)، میتوانید سریالساز خود را بنویسید. برای این کار باید متدهای serialize() و deserialize() را پیادهسازی کنید و همچنین descriptor — توضیح ساختار برای طرح فرمت را ارائه دهید.
سریالسازهای سفارشی به دو روش متصل میشوند: از طریق حاشیهنویسی @Serializable(with = MySerializer::class) برای اتصال به کلاس خاص یا به صورت سراسری از طریق Json { serializersModule = ... } برای اتصال به همه نمونههای نوع. روش دوم برای انواع داخلی (Date, UUID) ترجیح داده میشود تا مجبور نباشید روی هر فیلد حاشیهنویسی بنویسید.
// سریالساز سفارشی برای 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())
}
}
// استفاده از سریالساز سفارشی
@Serializable
data class Event(
val title: String,
@Serializable(with = DateSerializer::class)
val date: Date
)
fun main() {
val json = Json { prettyPrint = true }
val event = Event("انتشار", Date())
println(json.encodeToString(event))
}
در مثال DateSerializer java.util.Date را به رشته ISO 8601 تبدیل میکند. بدون سریالساز سفارشی kotlinx.serialization نمیتواند با Date کار کند — این نوعی است که در کتابخانه استاندارد Kotlin وجود ندارد. @Serializable(with = DateSerializer::class) روی یک فیلد خاص، سریالساز را فقط برای آن فیلد متصل میکند. برای ثبت سراسری همه Dateها از Json { serializersModule = SerializersModule { contextual(DateSerializer) } } استفاده کنید.
kotlinx.serialization فقط به JSON محدود نمیشود. کتابخانه از چهار فرمت داخلی پشتیبانی میکند که هر کدام ماژول و پیکربندی خاص خود را دارند. JSON (kotlinx-serialization-json) — عمومی، قابل خواندن توسط انسان، مناسب برای REST API. ProtoBuf (kotlinx-serialization-protobuf) — باینری، فشرده، با طرح اجباری، برای میکروسرویسهای پربار. CBOR (kotlinx-serialization-cbor) — معادل باینری JSON، مناسب برای IoT و دستگاههای همراه با ترافیک محدود. HOCON (kotlinx-serialization-hocon) — فرمت پیکربندی، سازگار با TypeSafe Config.
| فرمت | ماژول | نوع | طرح | کاربرد معمول |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | متنی | اختیاری | REST API، ذخیره داده |
| ProtoBuf | kotlinx-serialization-protobuf | باینری | اجباری (.proto) | میکروسرویسها، gRPC |
| CBOR | kotlinx-serialization-cbor | باینری | اختیاری | IoT، دستگاههای همراه |
| HOCON | kotlinx-serialization-hocon | متنی | اختیاری | فایلهای پیکربندی |
ProtoBuf نیاز به تعریف طرح در فایلهای .proto دارد، اما kotlinx-serialization-protobuf کلاسهای Kotlin را مستقیماً از @Serializable بدون .proto تولید میکند. این کار توسعه را ساده میکند: کافی است data class را حاشیهنویسی کنید و از ProtoBuf.encodeToByteArray() استفاده کنید. CBOR به ویژه برای چارچوب Android زمانی که نیاز به انتقال دادههای باینری فشرده از طریق NFC یا BLE دارید، مرتبط است. اندازه پیام CBOR به طور متوسط 20-30٪ از JSON کوچکتر است با همان مجموعه داده.
برای REST API در برنامه همراه JSON بهینه است — بدون ابزار اضافی اشکالزدایی میشود، در لاگها قابل خواندن است و با هر بکاندی سازگار است. اگر برنامه حجم زیادی از داده را بین میکروسرویسها منتقل میکند (صدها مگابایت) — ProtoBuf به لطف رمزگذاری باینری تا 5 برابر افزایش سرعت میدهد. برای ذخیره تنظیمات در فایلها از HOCON یا JSON استفاده کنید. برای دستگاههای با محدودیت ترافیک شدید (حسگرهای IoT) — CBOR.
خطای اول — نادیده گرفتن کلیدهای ناشناخته هنگام دسریالسازی. اگر سرور فیلد جدیدی اضافه کرده باشد و ignoreUnknownKeys = false باشد، برنامه با SerializationException سقوط میکند. به طور پیشفرض این پرچم خاموش است. راهحل: همیشه Json { ignoreUnknownKeys = true } را برای کد تولیدی تنظیم کنید تا در برابر تغییرات API مقاوم باشید.
خطای دوم — سریالسازی فیلدهای internal یا private در data class. در Kotlin data class همه فیلدهای سازنده اصلی به طور پیشفرض سریالسازی میشوند. اگر فیلد حاوی دادههای حساس است (رمز عبور، توکن)، باید آن را با @Transient علامتگذاری کنید یا از سازنده اصلی خارج کنید. @Transient فیلد را کاملاً از JSON حذف میکند، اما در سازنده ممکن است خطا ایجاد کند — بهتر است چنین فیلدی را در بدنه کلاس با @Transient تعریف کنید.
خطای سوم — سریالسازی چندریختی بدون sealed class. اگر به جای sealed از open class استفاده کنید، kotlinx.serialization نیاز به ثبت صریح همه وراث در serializersModule دارد. برخلاف sealed class که کامپایلر همه وراث را میشناسد، open class امکان گسترش دلخواه را میدهد — کتابخانه نمیتواند به طور خودکار همه زیرنوعها را تعیین کند. ثبت از طریق Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } } انجام میشود.
نسخه kotlinx.serialization باید با نسخه Kotlin سازگار باشد. JetBrains جدول سازگاری منتشر میکند: kotlinx-serialization 1.6.x با Kotlin 1.9.x، 1.7.x با Kotlin 2.0.x و 2.1.x سازگار است. عدم تطابق نسخهها باعث خطاهای کامپایل مرموز مانند "Symbol 'serializer' is missing" میشود. همیشه نسخه فعلی را در mavenCentral یا مخزن GitHub پروژه بررسی کنید.
سوالات متداول
kotlinx.serialization از تولید کد زمان کامپایل از طریق KSP استفاده میکند، در حالی که Gson و Moshi از بازتاب زمان اجرا استفاده میکنند. این مزیت در عملکرد (3-5 برابر سریعتر از Gson) و امنیت نوع را فراهم میکند. Gson هر فیلدی را بدون حاشیهنویسی سریالسازی میکند که میتواند منجر به نشت داده شود. kotlinx.serialization نیاز به حاشیهنویسی صریح @Serializable دارد که ایمنتر است. Moshi نیز از codegen پشتیبانی میکند، اما فقط برای JVM و Android.
بله، kotlinx.serialization کتابخانه رسمی چندسکویی JetBrains است. روی Kotlin/JVM (Android، Backend)، Kotlin/Native (iOS)، Kotlin/JS (Web، React) و Kotlin/Wasm کار میکند. API برای همه پلتفرمها یکسان است: @Serializable + Json.encodeToString() در همه جا یکسان کار میکند. برای iOS تنظیمات اضافی لازم نیست — Kotlin/Native کد سریالسازی شده را به باینری بومی کامپایل میکند.
فیلدهای Nullable (String?) اگر مقدار در JSON وجود نداشته باشد یا null مشخص شده باشد، به صورت null دسریالسازی میشوند. برای فیلدهای non-nullable (String) بدون مقدار پیشفرض، عدم وجود فیلد در JSON باعث SerializationException میشود. اگر میخواهید مقادیر null وارد JSON نشوند، Json { encodeDefaults = false } را پیکربندی کنید. این کار همه فیلدهای برابر با default (از جمله null برای nullable) را از خروجی حذف میکند.
از @SerialName("snake_case_name") روی هر فیلدی که نامش با فرمت Kotlin متفاوت است استفاده کنید. همچنین برای Kotlin 2.0+ گزینه Json { namingStrategy = JsonNamingStrategy.SnakeCase } در دسترس است — تبدیل خودکار camelCase ↔ snake_case. این تنظیم روی همه فیلدها به طور همزمان اعمال میشود. اگر سفارشیسازی جزئی نیاز است، @SerialName را با استراتژی جهانی ترکیب کنید.
خیر، Flow و کوروتینها مستقیماً قابل سریالسازی نیستند — آنها اجرای ناهمگام را نشان میدهند، نه داده را. برای انتقال داده از Flow باید آن را از طریق .toList() در کوروتین به مجموعه جمعآوری کرده و مجموعه را سریالسازی کنید. به طور مشابه، Job، Deferred یا Continuation قابل سریالسازی نیستند. فقط data class — مدلهای داده بدون منطق رفتاری — را سریالسازی کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید