Kotlin: شرح التسلسل في كوتلن

آخر تحديث: 2026-08-26

تُولّد kotlinx.serialization أدوات التسلسل وقت الترجمة عبر إضافة مترجم — تحويل طلب Charlie ↔ JSON بدون انعكاس، وآمن الأنواع، وأسرع من Jackson. يعالج @SerialName تعيين الحقول في سطر واحد؛ وJson { ignoreUnknownKeys = true } يُهيّئ التسامح مع الأخطاء في سطر واحد.

1. ما ستتعلمه


2. قصة مطور حقيقي

(1) المشكلة: أعطال وقت التشغيل في التسلسل القائم على الانعكاس

استخدم Bob وضع الانعكاس في Jackson لتسلسل كائنات Order. أثناء إعادة الهيكلة، تمت إعادة تسمية orderId إلى id — فشل إلغاء تسلسل JSON بصمت (عدم تطابق اسم الحقل)، وضاعت 500 سجل طلب.

(2) حل التسلسل وقت الترجمة

KOTLIN
// Jackson: قائم على الانعكاس، أخطاء وقت التشغيل
@JsonAlias("order_id")  // سهل النسيان
data class Order(val orderId: String, ...)

// kotlinx.serialization: وقت الترجمة، أخطاء وقت الترجمة
@Serializable
data class Order(
    @SerialName("order_id") val id: String,  // المُترجم يتحقق!
    val total: Double
)

توليد أدوات التسلسل وقت الترجمة — تصبح تغييرات أسماء الحقول أخطاء ترجمة، وليس فقدان بيانات وقت التشغيل.


3. @Serializable وإضافة المترجم

(1) تهيئة Gradle

KOTLIN
// build.gradle.kts
plugins {
    kotlin("jvm") version "1.9.22"
    kotlin("plugin.serialization") version "1.9.22"  // إضافة مترجم التسلسل
}

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

(2) التسلسل الأساسي

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)

// تسلسل: كائن -> سلسلة JSON
val order = Order("ORD-001", 299.99, "CONFIRMED")
val json = Json.encodeToString(order)
// {"id":"ORD-001","total":299.99,"status":"CONFIRMED"}

// إلغاء تسلسل: سلسلة JSON -> كائن
val decoded = Json.decodeFromString<Order>(json)
// Order(id=ORD-001, total=299.99, status=CONFIRMED)

(3) الانعكاس مقابل التسلسل وقت الترجمة

البُعد Jackson (انعكاس) kotlinx.serialization
الآلية انعكاس وقت التشغيل توليد كود وقت الترجمة
الأمان أخطاء وقت التشغيل أخطاء وقت الترجمة
الأداء أبطأ أسرع (بدون عبء الانعكاس)
ProGuard يتطلب قواعد حفظ غير مطلوب
متعدد المنصات JVM فقط JVM / Native / JS

4. ترميز وفك ترميز JSON

(1) تهيئة Json

KOTLIN
// الافتراضي: الوضع المتشدد
val strictJson = Json  // يفشل مع المفاتيح غير المعروفة

// متساهل: تجاهل المفاتيح غير المعروفة (إصدارات API)
val lenientJson = Json {
    ignoreUnknownKeys = true        // تجاهل الحقول غير الموجودة في الفئة
    isLenient = true                // قبول JSON مشوّه
    encodeDefaults = true           // تضمين الحقول ذات القيم الافتراضية
    prettyPrint = true              // طباعة جميلة للإخراج
    prettyPrintIndent = "  "        // المسافة البادئة
    coerceInputValues = true        // استخدام القيمة الافتراضية عند null لحقل non-null
}

val json = lenientJson.encodeToString(order)

(2) خيارات تهيئة Json

الخيار الافتراضي الوصف
ignoreUnknownKeys false تجاهل حقول JSON غير الموجودة في الفئة
isLenient false تحليل متساهل (قبول JSON غير قياسي)
encodeDefaults false ترميز الحقول ذات القيم الافتراضية
prettyPrint false إخراج مُنسّق
coerceInputValues false استخدام القيمة الافتراضية عندما يستقبل حقل non-null قيمة null

5. تعيين الحقول والقيم الافتراضية

(1) تعيين الحقول @SerialName

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 (نفس الاسم)
)

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

(2) الحقول الاختيارية والقيم الافتراضية

KOTLIN
@Serializable
data class Order(
    @SerialName("order_id") val id: String,
    @SerialName("order_total") val total: Double,
    val status: String = "PENDING",               // اختياري بقيمة افتراضية
    val customer: String? = null,                  // اختياري يقبل null
    @SerialName("tax_rate") val taxRate: Double = 0.08
)

// الحقول الاختيارية المفقودة تستخدم القيم الافتراضية
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 لإجبار الحقول

KOTLIN
@Serializable
data class Order(
    @Required val id: String,       // يجب أن يكون موجودًا في JSON
    @Required val total: Double     // يجب أن يكون موجودًا في JSON
)
// غياب 'id' أو 'total' -> SerializationException

6. دعم التنسيقات المتعددة

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 (تنسيق تهيئة)
import kotlinx.serialization.hocon.Hocon

(1) مقارنة التنسيقات

التنسيق قابلية القراءة الحجم السرعة حالة الاستخدام
JSON عالية كبير متوسطة اتصالات API
ProtoBuf منخفضة (ثنائي) صغير سريع RPC عالي الأداء
CBOR منخفضة (ثنائي) متوسط سريع IoT / الأنظمة المدمجة
HOCON عالية متوسط متوسطة ملفات التهيئة

7. تدفق ترميز/فك ترميز التسلسل

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

    Note over Obj,Ser: ترميز
    Obj->>Ser: @Serializable properties
    Ser->>JSON: encodeToString()
    Note over JSON: {"order_id":"ORD-001",...}

    Note over Ser,Obj: فك الترميز
    JSON->>Ser: decodeFromString<Order>(json)
    Ser->>Obj: @Serializable constructor
    Note over Obj: Order(id=ORD-001,...)

8. مثال كامل: تسلسل JSON لـ OrderProcessor

KOTLIN
// ============================================
// OrderProcessor - تسلسل JSON
// الميزة: طلب ↔ JSON مع تعيين الحقول
// ============================================

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() {
    // إنشاء طلب
    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"
    )

    // تسلسل: طلب -> JSON
    println("=== تسلسل ===")
    val jsonString = orderJson.encodeToString(order)
    println(jsonString)

    // إلغاء تسلسل: JSON -> طلب
    println("\n=== إلغاء تسلسل ===")
    val decoded = orderJson.decodeFromString<Order>(jsonString)
    println("Order: ${decoded.id}, Total: \$${decoded.total} USD")
    println("Items: ${decoded.items.map { "${it.sku} x${it.quantity}" }}")

    // التعامل مع مفاتيح غير معروفة (إصدارات API)
    println("\n=== إصدارات API ===")
    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}")

    // JSON أدنى (الحقول المطلوبة فقط)
    println("\n=== 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}")
}

الإخراج:

TEXT 📖 للعرض فقط
=== تسلسل ===
{
  "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"
}

=== إلغاء تسلسل ===
Order: ORD-001, Total: $299.99 USD
Items: [SKU-WIDGET x3, SKU-GADGET x1]

=== إصدارات API ===
Decoded with extra fields: ORD-002

=== JSON أدنى ===
Minimal: ORD-003, Status: PENDING, Customer: null

9. أمثلة عملية سريعة

▶ مثال: @Serializable أساسي

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

@Serializable
data class User(
    val id: Int,
    val name: String,
    val email: String,
    val active: Boolean = true
)

val user = User(1, "Alice", "alice@example.com")

// تسلسل إلى JSON
val json = Json.encodeToString(user)
println("JSON: $json")

// إلغاء التسلسل من JSON
val decoded = Json.decodeFromString<User>(json)
println("Decoded: $decoded")

// مع Pretty printing
val prettyJson = Json { prettyPrint = true }.encodeToString(user)
println("Pretty:\n$prettyJson")

**المخرجات:

TEXT 📖 للعرض فقط
JSON: {"id":1,"name":"Alice","email":"alice@example.com","active":true}
Decoded: User(id=1, name=Alice, email=alice@example.com, active=true)
Pretty:
{
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "active": true
}

▶ مثال: تسلسل متداخل وقوائم

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

@Serializable
data class Address(
    val street: String,
    val city: String,
    val country: String,
    val zipCode: String? = null
)

@Serializable
data class Company(
    val name: String,
    val address: Address,
    val employees: List<Employee> = emptyList(),
    val tags: Set<String> = emptySet()
)

@Serializable
data class Employee(
    val id: Int,
    val name: String,
    val role: String
)

val company = Company(
    name = "Tech Corp",
    address = Address("123 Main St", "NYC", "US"),
    employees = listOf(
        Employee(1, "Alice", "Engineer"),
        Employee(2, "Bob", "Designer")
    ),
    tags = setOf("tech", "startup")
)

val json = Json.encodeToString(company)
println("Company JSON:\n${Json { prettyPrint = true }.encodeToString(company)}")

// إلغاء التسلسل
val restored = Json.decodeFromString<Company>(json)
println("\nRestored: $restored")

**المخرجات:

TEXT 📖 للعرض فقط
Company JSON:
{
    "name": "Tech Corp",
    "address": {
        "street": "123 Main St",
        "city": "NYC",
        "country": "US",
        "zipCode": null
    },
    "employees": [
        {"id": 1, "name": "Alice", "role": "Engineer"},
        {"id": 2, "name": "Bob", "role": "Designer"}
    ],
    "tags": ["tech", "startup"]
}

Restored: Company(name=Tech Corp, address=Address(street=123 Main St, city=NYC, country=US, zipCode=null), employees=[Employee(id=1, name=Alice, role=Engineer), Employee(id=2, name=Bob, role=Designer)], tags=[tech, startup])

▶ مثال: SerialName و default values

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

@Serializable
data class ApiResponse(
    @SerialName("status_code") val statusCode: Int,
    @SerialName("data") val payload: String,
    @SerialName("timestamp") val time: Long = System.currentTimeMillis()
)

val response = ApiResponse(200, "OK")
val json = Json.encodeToString(response)
println(json)

val decoded = Json.decodeFromString<ApiResponse>(json)
println("Decoded: $decoded")

// خصائص اختيارية مع قيم افتراضية
@Serializable
data class Config(
    val name: String,
    val maxConnections: Int = 100,
    val timeout: Long = 30000,
    val debug: Boolean = false
)

val minimalJson = """{"name":"MyApp"}"""
val config = Json.decodeFromString<Config>(minimalJson)
println("Config: $config")

// ignoreUnknownKeys - تجاهل الحقول الإضافية
val json1 = """{"name":"Test","unknownField":"ignored","maxConnections":50}"""
val config1 = Json { ignoreUnknownKeys = true }
    .decodeFromString<Config>(json1)
println("With ignore: $config1")

**المخرجات:

TEXT 📖 للعرض فقط
{"status_code":200,"data":"OK","timestamp":1700000000000}
Decoded: ApiResponse(statusCode=200, payload=OK, time=1700000000000)
Config: Config(name=MyApp, maxConnections=100, timeout=30000, debug=false)
With ignore: Config(name=Test, maxConnections=50, timeout=30000, debug=false)

▶ مثال: sealed class للتسلسل

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

@Serializable
sealed class Result {
    abstract val message: String

    @Serializable
    data class Success(val data: String, override val message: String = "OK") : Result()

    @Serializable
    data class Failure(val errorCode: Int, override val message: String) : Result()

    @Serializable
    data class Loading(val progress: Int = 0, override val message: String = "Loading...") : Result()
}

val results: List<Result> = listOf(
    Result.Success("User loaded"),
    Result.Failure(404, "Not found"),
    Result.Loading(50)
)

val json = Json.encodeToString(results)
println("Sealed class JSON:\n${Json { prettyPrint = true }.encodeToString(results)}")

val restored: List<Result> = Json.decodeFromString(json)
println("\nRestored: $restored")

**المخرجات:

TEXT 📖 للعرض فقط
Sealed class JSON:
[
    {"type": "Result.Success", "data": "User loaded", "message": "OK"},
    {"type": "Result.Failure", "errorCode": 404, "message": "Not found"},
    {"type": "Result.Loading", "progress": 50, "message": "Loading..."}
]

Restored: [Success(data=User loaded, message=OK), Failure(errorCode=404, message=Not found), Loading(progress=50, message=Loading...)]

▶ مثال: Enums و maps

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

@Serializable
enum class Priority {
    LOW, MEDIUM, HIGH, CRITICAL
}

@Serializable
data class Task(
    val id: String,
    val title: String,
    val priority: Priority,
    val tags: List<String> = emptyList(),
    val metadata: Map<String, String> = emptyMap()
)

val task = Task(
    id = "T001",
    title = "Deploy to production",
    priority = Priority.CRITICAL,
    tags = listOf("deploy", "urgent"),
    metadata = mapOf("env" to "prod", "version" to "1.0.0")
)

val json = Json.encodeToString(task)
println(json)

val restored = Json.decodeFromString<Task>(json)
println("Restored: $restored")
println("Priority: ${restored.priority}")

**المخرجات:

TEXT 📖 للعرض فقط
{"id":"T001","title":"Deploy to production","priority":"CRITICAL","tags":["deploy","urgent"],"metadata":{"env":"prod","version":"1.0.0"}}
Restored: Task(id=T001, title=Deploy to production, priority=CRITICAL, tags=[deploy, urgent], metadata={env=prod, version=1.0.0})
Priority: CRITICAL

▶ مثال: تحويلات مخصصة (Custom Serializers)

KOTLIN
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*

// نوع DateTime مخصص
@Serializable(with = LocalDateTimeSerializer::class)
data class LocalDateTime(val iso: String) {
    fun toReadable(): String = iso.replace("T", " ").substring(0, 16)
}

object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
    override val descriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: LocalDateTime) {
        encoder.encodeString(value.iso)
    }

    override fun deserialize(decoder: Decoder): LocalDateTime {
        return LocalDateTime(decoder.decodeString())
    }
}

val event = LocalDateTime("2026-07-27T15:30:00")
val json = Json.encodeToString(event)
println("JSON: $json")

val restored = Json.decodeFromString<LocalDateTime>(json)
println("Readable: ${restored.toReadable()}")

**المخرجات:

TEXT 📖 للعرض فقط
JSON: "2026-07-27T15:30:00"
Readable: 2026-07-27 15:30

▶ مثال: Generic types

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

// فئة عامة قابلة للتسلسل
@Serializable
data class Container<T>(
    val data: T,
    val timestamp: Long = System.currentTimeMillis()
)

// استخدام مع أنواع مختلفة
val stringContainer = Container("Hello", 1700000000000)
val intContainer = Container(42, 1700000000001)

val json1 = Json.encodeToString(stringContainer)
val json2 = Json.encodeToString(intContainer)
println("String container: $json1")
println("Int container: $json2")

// إلغاء التسلسل - يجب تحديد النوع
val restored1: Container<String> = Json.decodeFromString(json1)
val restored2: Container<Int> = Json.decodeFromString(json2)
println("Restored 1: $restored1")
println("Restored 2: $restored2")

// List قابل للتسلسل
val orders = listOf(
    Container("Order 1"),
    Container("Order 2"),
    Container("Order 3")
)
val listJson = Json.encodeToString(orders)
println("\nList: $listJson")

**المخرجات:

TEXT 📖 للعرض فقط
String container: {"data":"Hello","timestamp":1700000000000}
Int container: {"data":42,"timestamp":1700000000001}
Restored 1: Container(data=Hello, timestamp=1700000000000)
Restored 2: Container(data=42, timestamp=1700000000001)

List: [{"data":"Order 1","timestamp":1700000000017},{"data":"Order 2","timestamp":1700000000017},{"data":"Order 3","timestamp":1700000000017}]

❓ أسئلة شائعة

س هل يمكن استخدام kotlinx.serialization جنباً إلى جنب مع Jackson؟
ج نعم، لكن لا يُنصح بذلك. تختلف آليتا التسلسل. أثناء الانتقال، يمكنك استخدام @JsonAlias كجسر؛ للمشاريع الجديدة، انتقل مباشرة إلى kotlinx.serialization.
س هل يمكن تسلسل الفئات المختومة (sealed classes)؟
ج نعم. الفئات المختومة @Serializable تتضمن تلقائيًا مميز نوع ("type":"SubClassName")، ويتم اختيار الفئة الفرعية الصحيحة أثناء إلغاء التسلسل.
س كيف أخصص منطق التسلسل؟
ج حدد أداة تسلسل مخصصة باستخدام @Serializable(with = CustomSerializer::class)، أو نفّذ واجهة KSerializer<T> للتحكم يدويًا في الترميز/فك الترميز.
س هل يدعم التسلسل الأنواع العامة (generics)؟
ج نعم، لكنك تحتاج إلى توضيح الفئة العامة بـ @Serializable. يتطلب decodeFromString معلمات نوع صريحة.
س لماذا هناك حاجة لإضافة المترجم؟
ج إضافة مترجم كوتلن تُولّد تلقائيًا كود أداة التسلسل للفئات @Serializable وقت الترجمة، متجنبة الانعكاس وقت التشغيل. هذا هو أساس أدائها وأمانها.
س ما الفرق بين @SerialName و @JsonProperty؟
ج وظيفيًا متشابهان (تعيين اسم حقل JSON)، لكن @SerialName هو توضيح kotlinx.serialization المُعالج وقت الترجمة؛ و@JsonProperty هو توضيح Jackson المُعالج وقت التشغيل عبر الانعكاس.

📖 ملخص


📝 تمارين

  1. مبتدئ (⭐): أضف @Serializable إلى Order، سلسّل إلى JSON، وألغِ التسلسل. تلميح: @Serializable data class Order(...)
  2. متوسط (⭐⭐): استخدم @SerialName لتعيين أسماء حقول Order (order_id، order_total)، وتهيئة Json { ignoreUnknownKeys = true } لتطور API. تلميح: راجع القسم 5
  3. متقدم (⭐⭐⭐): نفّذ أداة تسلسل مخصصة تسطّح عناصر Order في تنسيق JSON مسطح (توسيع العناصر إلى تنسيق item_1_sku، item_1_qty). تلميح: نفّذ KSerializer<Order>

← السابق | التالي →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%