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. في المرحلة الثالثة، يتم تجميع الكود المُنشأ مع الكود المصدري للمشروع. ونتيجة لذلك، لا يتم تنفيذ أي من هذه المراحل أثناء تشغيل التطبيق.
يعمل المسلسل المُنشأ مباشرة مع حقول الفئة من خلال أدوات الوصول (getters وsetters)، دون انعكاس. هذا يعني أن الحقول ذات المُعدِّل 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)، والقوائم، والكائنات المتداخلة، والتعدادات. جميع حقول الفئة إلزامية افتراضياً ما لم يُذكر خلاف ذلك.
يتم تكوين 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، تُستخدم القيمة الافتراضية. إذا وصلت حقول غير معروفة في JSON دون ignoreUnknownKeys = true، يتم طرح SerializationException.
Sealed class هي واحدة من أقوى حالات استخدام kotlinx.serialization. تدعم المكتبة التسلسل متعدد الأشكال للتسلسلات الهرمية لـ sealed class دون تكوين إضافي: يكفي إضافة التعليق التوضيحي @Serializable إلى sealed class وجميع فئاتها الفرعية. أثناء التسلسل، يُضاف حقل «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 ضروري عند العمل مع APIs حيث أسماء الحقول بصيغة snake_case بينما أسلوب Kotlin هو camelCase. على سبيل المثال، يرسل الخادم «user_id»، لكن في كود Kotlin يُستخدم userId. @SerialName(«user_id») يحل هذه المشكلة دون مُعيّنات إضافية. @Transient مفيد للحقول التي لا يجب إرسالها إلى الخادم — مثل القيم المحسوبة المؤقتة أو ذاكرة التخزين المؤقت.
افتراضياً، جميع الحقول في kotlinx.serialization إلزامية. إذا كان الحقل قد يكون غائباً في JSON، يجب جعله nullable (String?) أو تعيين قيمة افتراضية (val name: String = «»). ومع ذلك، هناك حالات يكون فيها الحقل غير nullable في Kotlin ولكنه قد يكون مفقوداً في JSON بسبب تغييرات إصدار API. في هذه الحالة، @Required يطرح SerializationException عند غياب الحقل، بينما القيمة الافتراضية تملؤه دون خطأ.
KSerializer هي الواجهة التي تنفذها جميع المسلسلات في kotlinx.serialization. إذا كان إنشاء الكود القياسي غير مناسب (على سبيل المثال، للعمل مع Date أو Bitmap أو تنسيق ثنائي محدد)، يمكنك كتابة المسلسل الخاص بك. للقيام بذلك، نفذ طريقتي serialize() وdeserialize()، وقدم واصفاً — وصفاً للهيكل لمخطط التنسيق.
تُربط المسلسلات المخصصة بطريقتين: عبر التعليق التوضيحي @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("Release", 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 | ثنائي | اختياري | إنترنت الأشياء، الأجهزة المحمولة |
| 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 يوفر ميزة سرعة تصل إلى 5x بفضل الترميز الثنائي. لتخزين الإعدادات في ملفات، استخدم HOCON أو JSON. للأجهزة ذات حدود حركة المرور الصارمة (مستشعرات IoT)، استخدم CBOR.
الخطأ الأول هو تجاهل المفاتيح غير المعروفة أثناء إلغاء التسلسل. إذا أضاف الخادم حقلاً جديداً وكان لديك ignoreUnknownKeys = false، فسيتعطل التطبيق مع SerializationException. هذه العلامة معطلة افتراضياً. الحل: اضبط دائماً Json { ignoreUnknownKeys = true } لكود الإنتاج لتكون مقاوماً لتغييرات API.
الخطأ الثاني هو تسلسل الحقول internal أو private في data class. في data class لـ Kotlin، جميع الحقول في المنشئ الرئيسي تُسلسل افتراضياً. إذا كان الحقل يحتوي على بيانات حساسة (كلمة مرور، رمز مميز)، فيجب وضع علامة @Transient عليه أو إخراجه من المنشئ الرئيسي. @Transient يستبعد الحقل من JSON بالكامل، ولكن داخل المنشئ قد يسبب خطأ — من الأفضل تعريف هذه الحقول في جسم الفئة مع @Transient.
الخطأ الثالث هو التسلسل متعدد الأشكال دون sealed class. إذا كنت تستخدم open class بدلاً من sealed، يتطلب 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». تحقق دائماً من الإصدار الحالي على Maven Central أو في مستودع 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 يجمّع الكود المسلسل إلى ملف ثنائي أصلي.
الحقول القابلة للفارغية (String?) تُفك تسلسل كـ null إذا كانت القيمة مفقودة أو null في JSON. للحقول غير القابلة للفارغية (String) بدون قيمة افتراضية، سيؤدي غياب الحقل في JSON إلى طرح SerializationException. إذا كنت تريد ألا تظهر القيم null في JSON، قم بتكوين Json { encodeDefaults = false }. هذا يستبعد جميع الحقول المساوية للقيمة الافتراضية (بما في ذلك null للأنواع القابلة للفارغية).
استخدم @SerialName(«اسم_بصيغة_snake_case») على كل حقل يختلف اسمه عن تنسيق Kotlin. بدلاً من ذلك، لـ Kotlin 2.0+ يتوفر Json { namingStrategy = JsonNamingStrategy.SnakeCase } للتحويل التلقائي camelCase ↔ snake_case. هذا الإعداد ينطبق على جميع الحقول مرة واحدة. إذا كانت هناك حاجة لتخصيص جزئي، ادمج @SerialName مع الاستراتيجية العالمية.
لا، Flow وcoroutines غير قابلين للتسلسل مباشرة — يمثلان تنفيذاً غير متزامن، وليس بيانات. لنقل البيانات من Flow، اجمعها في مجموعة عبر .toList() في coroutine وسلسل المجموعة. وبالمثل، لا يمكن تسلسل Job أو Deferred أو Continuation. سلسل فقط data classes — نماذج بيانات بدون منطق سلوكي.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا