03. JSON Schema va Component Tree
⚡ Server-Driven UI (SDUI) — Mukammal Enterprise & System Design
03. JSON Schema va Component Tree: Server Bilan Qat'iy Shartnoma
"Agar schema'da noaniqlik yoki bo'shliq bo'lsa, mobil ilovada albatta crash yuz beradi. SDUI tizimlarida Schema — bu backend va mobile injiniring jamoalari o'rtasidagi konstitutsiyadir."
1. Component Tree Anatomiyasi: Serverdan Keluvchi Iyerarxiya
Server-Driven UI tizimlarida har bir ekran daraxtsimon komponentlar strukturasi (Component Tree) ko'rinishida taqdim etiladi.
Quyidagi diagrammada SDUI ekranining iyerarxik tuzilishi tasvirlangan:
graph TD
Root["Screen Root (Dashboard)"] --> Header["Section: Header (Sticky)"]
Root --> Body["Section: Body (Lazy Scroll)"]
Root --> Bottom["Section: Bottom Bar (Pinned)"]
Header --> UserWidget["Component: UserProfileHeader\nid: 'u_101', type: 'profile_header'"]
Body --> Carousel["Component: PromoCarousel\nid: 'c_201', type: 'carousel_banner'"]
Body --> QuickActions["Component: QuickActionsGrid\nid: 'q_301', type: 'actions_grid'"]
Body --> Feed["Component: DynamicTransactionFeed\nid: 'f_401', type: 'history_list'"]
Bottom --> CTA["Component: PrimaryActionButton\nid: 'btn_501', type: 'action_button'"]
style Root fill:#131720,stroke:#bef264,stroke-width:2px
style Header fill:#181d26,stroke:#7da2ce,stroke-width:1px
style Body fill:#181d26,stroke:#7da2ce,stroke-width:1px
style Bottom fill:#181d26,stroke:#7da2ce,stroke-width:1px
2. To'liq JSON Schema Spetsifikatsiyasi
Professional SDUI arxitekturasida server ixtiyoriy JSON yubora olmaydi. U qat'iy tekshirilgan JSON Schema shartnomasiga bo'ysunadi:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"screenId": "dashboard_home",
"version": "2.1.0",
"minSupportedClientVersion": "1.4.0",
"refreshPolicy": {
"ttlSeconds": 180,
"strategy": "cache_first",
"backgroundRefresh": true
},
"layout": {
"type": "scroll_view",
"showsIndicators": false,
"bounce": true
},
"components": [
{
"id": "header_profile_01",
"type": "user_profile_header",
"layout": {
"padding": { "top": 16, "leading": 20, "bottom": 12, "trailing": 20 },
"zIndex": 10
},
"props": {
"avatarUrl": "https://cdn.azimjondev.uz/avatars/user_102.jpg",
"userName": "Azimjon Dasturchi",
"badgeText": "PRO",
"badgeColor": "token.accentSuccess",
"balanceFormatted": "14,500,000 UZS"
},
"accessibility": {
"label": "Azimjon Dasturchi, balansingiz 14 million 500 ming so'm",
"trait": "header"
},
"actions": [
{
"event": "onTap",
"type": "navigate",
"route": "app://settings/profile",
"presentation": "push"
}
]
},
{
"id": "promo_banner_carousel",
"type": "carousel_banner",
"layout": {
"margin": { "top": 8, "bottom": 16 }
},
"props": {
"autoScrollInterval": 4.5,
"aspectRatio": 2.1,
"items": [
{
"id": "banner_ramadan",
"imageUrl": "https://cdn.azimjondev.uz/banners/spring.png",
"route": "app://shop/category/tech"
}
]
}
}
]
}
3. Polymorphic Decoding Fojiasi va Crashlarning Oldini Olish
Swift tilida SDUI modellari bilan ishlashda eng katta halokatli xato — bu polimorfik massivlarni oddiy Decodable enum bilan parslashdir:
// ❌ HALOKATLI XATO (ANTI-PATTERN):
enum ComponentPayload: Decodable {
case profileHeader(ProfileHeaderProps)
case carousel(CarouselProps)
// Ertaga backend yangi 'cryptoBanner' qo'shsa...
}
Nega bu butun ilovani qulatadi?
flowchart TD
subgraph Bad["1. Oddiy Swift JSONDecoder (Falokat)"]
RawJSON["JSON: [Profile, Carousel, CryptoWidget]"] --> Parser1["JSONDecoder.decode([Component].self)"]
Parser1 -->|"CryptoWidget tanilmadi!"| Fail["DecodingError.dataCorrupted"]
Fail --> WhiteScreen["💥 BUTUN MASSIV QULAYDI: Foydalanuvchiga OQ EKRAN!"]
end
subgraph Good["2. Safe Failable Array Decoding (Professional)"]
RawJSON2["JSON: [Profile, Carousel, CryptoWidget]"] --> Parser2["SafeDecodableArray"]
Parser2 --> Success1["✅ ProfileHeader -> Render"]
Parser2 --> Success2["✅ Carousel -> Render"]
Parser2 --> Fallback["⚠️ CryptoWidget -> Noma'lum! Tashlab ketiladi"]
Success1 --> SmoothUI["🚀 EKRAN 99% MUKAMMAL ISHLAYDI!"]
Success2 --> SmoothUI
end
style Bad fill:#251014,stroke:#f87171,stroke-width:1px
style Good fill:#0d1f14,stroke:#4ade80,stroke-width:1px
Swift 6 da Ishlovchi "Safe Failable Array Decoding" Kodi:
import Foundation
// MARK: - 1. Safe Property Wrapper
@propertyWrapper
public struct SafeDecodableArray<T: Decodable>: Decodable, Sendable where T: Sendable {
public var wrappedValue: [T]
public init(wrappedValue: [T]) {
self.wrappedValue = wrappedValue
}
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements: [T] = []
while !container.isAtEnd {
do {
let element = try container.decode(T.self)
elements.append(element)
} catch {
#if DEBUG
print("[SDUI Defensive Decoding] Noma'lum yoki buzilgan komponent tashlab ketildi: \(error)")
#endif
// Noma'lum elementni xavfsiz o'tkazib yuborish
_ = try? container.decode(DummyCodable.self)
}
}
self.wrappedValue = elements
}
}
private struct DummyCodable: Decodable {}
// MARK: - 2. Asosiy Ekran Modeli
public struct SDUIScreenPayload: Decodable, Sendable {
public let screenId: String
public let version: String
public let minSupportedClientVersion: String?
// Xavfsiz failable massiv
@SafeDecodableArray
public var components: [ComponentNode]
enum CodingKeys: String, CodingKey {
case screenId
case version
case minSupportedClientVersion
case components
}
}
// MARK: - 3. Komponent Modeli
public struct ComponentNode: Decodable, Identifiable, Sendable {
public let id: String
public let type: String
public let layout: ComponentLayout?
public let props: [String: AnyCodable]
public let actions: [ActionNode]?
public let accessibility: AccessibilityNode?
}
4. Design Tokens: Nega Qat'iy Hex Kodlar Katta Xato?
Serverdan "color": "#FF0000" (qattiq hex kod) yuborish SDUI da eng xavfli anti-patternlardan biridir.
Qattiq Hex Kodlarning 3 Ta Zarari:
- Dark Mode Fojiasi: Tungi rejimda server har bir element uchun o'zi rang hisoblab chiqishi mumkin emas (tarmoq trafigi va arxitektura buziladi).
- Apple Dynamic Type: Shrift
"fontSize": 14bo'lsa, iOS sozlamalarida shriftni kattalashtirgan foydalanuvchida yozuvlar bir-birining ustiga minib ketadi. - Rebranding: Kompaniya brend rangini och yashildan to'q yashilga o'zgartirsa, serverdagi barcha tarixiy JSON fayllarni tahrirlab chiqish kerak bo'ladi.
To'g'ri Yechim: Semantic Design Tokens
Server faqat semantik nom (token) yuboradi:
{
"textColor": "token.textPrimary",
"backgroundColor": "token.surfaceElevated",
"typography": "token.headlineMedium",
"cornerRadius": "token.radiusLarge"
}
iOS klientida bu semantik tokenlar tizimning native ranglariga map qilinadi:
import SwiftUI
public enum DesignTokens {
public static func color(for token: String) -> Color {
switch token {
case "token.textPrimary":
return Color.primary
case "token.textSecondary":
return Color.secondary
case "token.surfaceElevated":
return Color(uiColor: .secondarySystemBackground)
case "token.accentSuccess":
return Color.green
case "token.accentDanger":
return Color.red
default:
return Color.primary // Fallback
}
}
public static func font(for token: String) -> Font {
switch token {
case "token.headlineLarge":
return Font.system(.title2, design: .rounded, weight: .bold)
case "token.headlineMedium":
return Font.system(.headline, design: .default, weight: .semibold)
case "token.bodyRegular":
return Font.body
case "token.caption":
return Font.caption
default:
return Font.body
}
}
}
5. Breaking vs Non-Breaking Schema Changes
Senior dasturchi doimo quyidagi qoidaga rioya qilishi shart:
| O'zgarish Turi | Xususiyati | Holati | Natija |
|---|---|---|---|
| Yangi maydon (Field) qo'shish | Optional (field: String?) | ✅ Non-Breaking | Eski ilovalar buni shunchaki e'tiborsiz qoldiradi |
| Yangi komponent turi qo'shish | SafeDecodableArray bilan | ✅ Non-Breaking | Yangi ilovalar chizadi, eski ilovalarda fallback ishlaydi |
| Maydon turini o'zgartirish | price: 15000 (Int) -> price: "15,000" (String) | ❌ CRITICAL BREAKING! | Eski ilovalarda parslash xatosi va crash |
| Majburiy maydonni o'chirish | id yoki type ni olib tashlash | ❌ CRITICAL BREAKING! | Butun komponent parslanishdan to'xtaydi |
6. Interaktiv Savol-Javob (Self-Check Quiz)
<details> <summary><b>1-Savol: Nega JSON Schema'da har bir komponentda majburiy unikal 'id' bo'lishi shart?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: SwiftUI'da ForEach yoki UIKit'da UICollectionViewDiffableDataSource ekranni chizayotganda elementlarni identifikatsiyalashi kerak (Identifiable). Agar id bo'lmasa, serverdan yangi data kelganda butun List qayta chiziladi (re-created), bu esa ekranda miltillash (flicker) va 120 FPS dan 30 FPS ga tushib qolishni keltirib chiqaradi. </details>
<details> <summary><b>2-Savol: Backend maydon turini o'zgartirishi kerak (masalan, Int dan String ga). Buni eski ilovalarni sindirmasdan qanday qilish mumkin?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: Eski maydonni o'zgartirmasdan, yangi nom bilan yangi maydon qo'shiladi:
{
"price": 15000, // Eski ilovalar uchun saqlanadi
"priceFormatted": "15,000 UZS" // Yangi versiyalar uchun qo'shiladi
}
Eski ilovalar o'zining price qiymatini o'qishda davom etadi, yangi ilovalar esa priceFormatted ni oladi. 1 yildan keyin eski maydon o'chirilishi mumkin. </details>
7. Amaliy Topshiriq (Senior Challenge)
O'z kompyuteringizda SafeDecodableArray testini yozing:
- 3 ta to'g'ri
ComponentNodeva 2 ta xato strukturaga ega bo'lgan JSON test string yarating. JSONDecoder().decode(SDUIScreenPayload.self, from: data)orqali dekod qiling.- Assert qiling: natijada array crash bo'lmasdan aynan 3 ta elementni qabul qilganini Unit Test orqali tasdiqlang!