CodeGym /Kurslar /Kotlin SELF /Esas pipeline encode/ decode + JSON-un faylda saxlanmasi...

Esas pipeline encode/ decode + JSON-un faylda saxlanmasi

Kotlin SELF
Səviyyə, Dərs
Mövcuddur

1. Bu gun ne qururuq: obyekt → JSON setri → fayl → obyekt

Ilk defe “obyekti fayla” ifadəsini esidende beyin tez-tez “deyiseni fayla soxdum — sabah eyni ile cixardim” tipli sehrli bir ssenari tesvir edir. Realliqda sehr yoxdur: fayl bayt/metn saxlayir, Kotlin obyektlerini yox. Buna gore biz tamamile aciq bir zencir quracagiq: evvelce obyekti JSON setrine ceviririk, sonra bu setri fayla yaziriq. Daha sonra — setri fayldan oxuyur ve yeniden obyekta ceviririk.

Numuneler bir tetbiq kimi baglansin deyə, şertli konsol mini-layihəmizi davam etdirek: xerc ucoutu (Expense Tracker). Helelik baza yoxdur, sebekeler yoxdur ve “boyuk” arxitektura da yoxdur — indi bizim ucun daha vacibi odur ki, melumatlari ele saxlayib-yukleyek ki, sabah tetbiq “temiz vereqden” baslamasin ve kederli println("Eee, yenə her seyi el ile?") cixarmasin.

Pipeline sxemi

flowchart LR
    A[Expense / List⟨Expense⟩
yaddaşda] --> B[encodeToString
JSON String] B --> C[writeText
expenses.json] C --> D[readText
JSON String] D --> E[decodeFromString
List⟨Expense⟩]

2. Addim 1: obyekt → JSON setri

Biz “JSON-a serializasiya et” deyende, eslinde bunu deyirik: “obyektden setr duzelt ki, onu konsolda gostermek, fayla yazmaq, hara istersen gondermek olsun”. Burada cox vacibdir ki, sehv salmayasiniz: toString() da setr verir, amma bu melumat formati deyil, sadəcə “insan ve debug ucun” texniki temsilidir. JSON ise formatdir: kitabxana onu geri oxuya bilir ve onu bir muqavile (contract) kimi qebul etmek olar.

Tetbiqimiz ucun mini-model

Tutaq ki, kecen dersde (@Serializable haqqinda) biz artiq bele bir xerc modeli duzeltmisdik:

import kotlinx.serialization.Serializable

@Serializable
data class Expense(
    val id: Int,
    val title: String,
    val amount: Int,
    val category: String,
    val note: String? = null,
)

Burada her sey maksimum sadedir: id — tam eded, title — setr, amount — yine tam eded (meselen, qepiksiz frank/dollar — ideal deyil, amma baslangic ucun yeterlidir), category — setr, note — nullable, null ile mesq etmek ucun.

En sade kodlama: bir obyekt

Evvelce faylsiz ve siyahisiz — “umumiyyetle ne alinır” gormek ucun. Setre kodlama ucun Json.encodeToString(...) istifade olunur:

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

fun main() {
    val e = Expense(id = 1, title = "Kofe", amount = 250, category = "Yemek")
    val jsonText = Json.encodeToString(e)

    println(jsonText)
    // {"id":1,"title":"Kofe","amount":250,"category":"Yemek","note":null}
}

Diqqet edin: JSON “six” alindi, setir kecidleri olmadan. Bu normaldir. Onu insan ucun gozellesdirmeyi (pretty print) biz novbeti dersde, Json { ... } sazlamalari haqqinda danışanda edeciyik, indi ise bizim ucun vacib olan odur ki, o saxlama formati kimi etibarli islesin.

Xercler siyahisinin kodlanmasi

Tetbiqimiz bir xerc deyil, adeten siyahi saxlayir. Siyahi ucun JSON massiv kimi gorunecek: [...], ve kitabxana bunu birbasa destekleyir:

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

fun main() {
    val expenses = listOf(
        Expense(1, "Kofe", 250, "Yemek"),
        Expense(2, "Gedis", 60, "Neqliyyat", note = "Metro"),
    )

    val jsonText = Json.encodeToString(expenses)
    println(jsonText)
    // [{"id":1,"title":"Kofe","amount":250,"category":"Yemek","note":null},{"id":2,"title":"Gedis","amount":60,"category":"Neqliyyat","note":"Metro"}]
}

Burada yadda saxlamaq ucun sade bir uygunluq faydalidir: List<Expense> JSON massivine cevrilir [...], her bir Expense ise daxilinde JSON obyektine cevrilir {...}.

3. Addim 2: JSON setri → obyekt

Indi ters istiqamete gedirik. Deserializasiya ideyasi sadedir: bizde JSON setri var ve biz lazim olan tipde Kotlin obyektini almaq isteyirik. Vacibdir ki, tipi gostermek lazimdir. JSON setri oz-ozune demir “men User-em” ve ya “men Expense-em”. O sadəcə melumatdir, tip ise sizin kodunuzun qeraridir.

Illuziya qurmayin deyə, duzunu deyim: eger JSON modele uygun deyilse, bu addim xeta ile yixila biler. Bu normaldir. Biz elə “giris melumati her zaman ‘kirli’ ola biler” dunyasinda yashamagi oyrənirik.

Dekodlama ucun Json.decodeFromString<T>(...) istifade olunur.

Bir obyekti dekod edirik

import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

fun main() {
    val input = """{"id":10,"title":"Chay","amount":120,"category":"Yemek","note":null}"""
    val e = Json.decodeFromString<Expense>(input)

    println(e)
    // Expense(id=10, title=Chay, amount=120, category=Yemek, note=null)
}

<Expense> hissəsinə diqqet edin: biz aciq deyirik: “bunu Expense kimi oxu”.

Siyahini dekod edirik

import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

fun main() {
    val input = """[{"id":1,"title":"Kofe","amount":250,"category":"Yemek","note":null}]"""
    val expenses = Json.decodeFromString<List<Expense>>(input)

    println(expenses.size)     // 1
    println(expenses[0].title) // Kofe
}

Eger {} ile [] qarishdirsaniz, xeta olacaq. Bu en cox rast gelinen bug-lardandir: siz fikirləşirsiniz ki, faylda bir obyekt var, amma orada massivdir, ve ya eksine. Ona gore dekod etmeden once en azi “gozle” yoxlamaq faydalidir: setr nədən baslayir — { yoxsa [.

4. Addim 3: fayl setr ucun transport kimi

Bu merhelede vacib psixoloji deyisiklik edirik: fayl “obyektleri saxlamir”, fayl “setr saxlayir”. Biz sadəcə razilasmişıq ki, bu setr JSON olacaq.

Kod baximindan bu o demekdir ki, emeliyyatlar iki musteqil hissəyə bolunur: serializasiya (setre) ve I/O (fayla). Bu bolunme debugging-ə cox komek edir: eger nese xarab oldusa, siz dərhal anlayirsiniz — problem JSON-dadir, yoxsa yol/huquqlar/direktoriya meselesindedir.

JSON setrinin fayla yazilmasi

import java.io.File
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

fun main() {
    val dir = File("data")
    dir.mkdirs()

    val file = File(dir, "expenses.json")
    val text = Json.encodeToString(listOf(Expense(1, "Kofe", 250, "Yemek")))

    file.writeText(text)
    println("Saved to ${file.path}") // Saved to data/expenses.json
}

Esas meqam — dir.mkdirs(): eger qovluq yoxdursa, yazma emeliyyati xeta ile bitə biler. Beli, komputer bezen sizden evvelce qovluq yaratmaginizi ister. O inadkardir, amma ədalətlidir.

JSON setrinin fayldan oxunmasi

import java.io.File

fun main() {
    val file = File("data/expenses.json")
    val text = file.readText()

    println(text) // JSON mezmunu bir setr kimi
}

Burada her sey maksimum duzdur: setri oxuduq — ve sonra onu decodeFromString-a vermek olar.

5. Mini-pipeline: siyahini saxla ve yukle

Indi her seyi iki addimda birlesdirek: siyahini saxla, sonra yukle. Kodu oxumaq ve yeniden istifade etmek asan olsun deyə, bunu kicik funksiyalara cixaracagiq. Bu “esrler ucun arxitektura” deyil, sadəcə insani vərdişdir: eyni kodu uc yerde yazmamaq.

Saxlama funksiyasi

import java.io.File
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

fun saveExpenses(file: File, expenses: List<Expense>) {
    val text = Json.encodeToString(expenses)
    file.parentFile?.mkdirs()
    file.writeText(text)
}

Burada kicik bir mudafie edirik: file.parentFile?.mkdirs() direktoriyani yaradacaq (eger o varsa ve parentFile null deyilse). Bu, "data/expenses.json" kimi bir yol verdiyiniz zaman rahatdir.

Yukleme funksiyasi

import java.io.File
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

fun loadExpenses(file: File): List<Expense> {
    if (!file.exists()) return emptyList()

    val text = file.readText()
    return Json.decodeFromString(text)
}

Burada vacib UX meqami var: fayl yoxdursa, bu “dunyanin xetasi” deyil. Bu ilk ise dusme ucun normal veziyyetdir. Ona gore yixilmaq yerine emptyList() qaytaririq.

Diqqet edin: Json.decodeFromString(text) aciq <List<Expense>> olmadan isleyir, cunku kompilyator tipi funksiyanin qaytaris tipinden cixara bilir. Eger loadExpenses Any qaytarsaydi, tipi aciq yazmaq lazim olardi — amma bunu etməyecəyik; biz kompilyatorla dost olmaq isteyirik, mubarize aparmaq yox.

main-de yoxlayiriq ki, “ora-bura” isleyir

Indi kicik bir main yazaq: o xerc siyahisi yaradir, saxlayir, geri yukleyir ve neticeni cap edir. Bu vacib ritualdir: evvelce pipeline-in “vakumda” islediyine emin olmaq, sonra ise komandalara add/list/remove (siz bunlari daha onceki gunlerde etmis ola bilersiniz) birlesdirmek.

import java.io.File

fun main() {
    val file = File("data/expenses.json")

    val expenses = listOf(
        Expense(1, "Kofe", 250, "Yemek"),
        Expense(2, "Gedis", 60, "Neqliyyat", note = "Metro"),
    )

    saveExpenses(file, expenses)

    val loaded = loadExpenses(file)
    println("Loaded: ${loaded.size}") // Loaded: 2
    println(loaded[0])                // Expense(id=1, title=Kofe, amount=250, category=Yemek, note=null)
}

Eger bu numune isleyirse, deməli baza hazirdir: biz melumatlari “ise dusmeler arasinda” saxlamağı oyrendik. Bu artiq demek olar ki, boyuk bir proqramdir (cox az qalıb: qeribe fayldan her defe sinmamağı oyrenmek).

Fayli CLI xerc ucoutuna inteqrasiya edirik

Siz (ve ya artiq etmisiniz) add, list, remove kimi komandalarla konsol interfeysi quranda, fayli duzgun inteqrasiya etmek vacibdir: yukleme proqramin evvelinde, saxlamani ise deyisikliklerden sonra etmek lazimdir. Eger saxlamani axira saxlasaniz, proqram daha once yixilsa — melumat itir. Eger her komandadan sonra saxlasaniz, itirmek demek olar ki, mumkun olmaz (beli, bu fayla bir az daha tez-tez yazir, amma tedris layihesi ucun bu normaldir).

Aşagida — komandalari murəkkeb parsing etmeden, çox sadələşdirilmiş main “skeleti” var. O, mehz ideyani gosterir: “yukledik → deyisdik → saxladik”.

import java.io.File

fun main() {
    val file = File("data/expenses.json")
    val expenses = loadExpensesSafe(file).toMutableList()

    expenses.add(Expense(3, "Pecenye", 90, "Yemek"))
    saveExpenses(file, expenses)

    println("Now total: ${expenses.size}") // Now total: 3
}

Burada vacib fikir: yaddaşda biz siyahi ile işleyirik (MutableList), amma faylda JSON setr saxlayiriq. Fayl siyahidan “agilli” deyil. O sadəcə serializasiyanin neticesini saxlayir.

6. Xetalar ve diaqnostika: yukleme harada yixilir

Her bir “melumati yukle” emeliyyati sert dunyada yashayir. Fayl olmaya biler, bos ola biler, zedelenmis ola biler, JSON olmaya biler, JSON ola biler ama lazim olan formatda olmaya biler. Eger “setri oxu” ve “JSON-u parse et” merhelelerini ayirmirsinizse, diaqnostika qehve telvesinden fal acmaga cevrilir (xerc tetbiqimizde bu xususiyle simvolikdir).

Iki sinif problem

Realliqda problemlər demek olar ki, her zaman iki boyuk kateqoriyaya bolunur:

Harada pozuldu Nece gorunur Insani dildə ne demekdir
I/O (fayl) tapilmadi, icaze yoxdur, fayl yerinə direktoriya “biz setri elde ede bilmedik”
JSON/tip parsing xetasi, tip uyğunsuzlugu “setri aldıq, amma model ile ust-uste dusmur”

try/catch ile minimal diaqnostika

loadExpenses-in ele bir versiyasini edek ki, yixilmasin, bos siyahi qaytarsin ve aydin mesaj cap etsin. Bu, butun proqramlar ucun ideal strategiya deyil, amma tedris konsol tetbiqi ucun ela baslangicdir.

import java.io.File
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

fun loadExpensesSafe(file: File): List<Expense> {
    if (!file.exists()) return emptyList()

    return try {
        val text = file.readText()
        Json.decodeFromString(text)
    } catch (e: Exception) {
        println("Failed to load ${file.path}: ${e.message}")
        emptyList()
    }
}

Niyə Exception tuturuq, daha dəqiq bir seyi yox? Cunku indi meqsedimiz — yixilmamaq ve mesaji gormekdir. Istisna tiplərini daha sonra dəqiqləşdirmək olar, ozunuze inam hiss etdikce. Bu səviyyede esas olan prinsipdir: “oxumaq” ve “parse etmek” ferqli addimlardir.

7. Tipik sehvler

Sehv №1: obyekti toString() ile “saxlamağa” calismaq.
data class ucun toString() cox rahatdir ve heqiqeten “demek olar ki, melumat kimidir”. Amma bu tələdır: bu debug ucun setrdir, saxlanma formati deyil. Bu gun siz Expense(id=1, title=Kofe, ...) saxladiniz, sabah sahələrin sirasini ve ya property adini deyisdiniz — ve “format” birden-bire ferqli oldu. JSON ise kitabxana terefinden yaradilir ve kitabxana terefinden oxunur, ona gore “ora-bura” dövrü heqiqeten menalidir.

Sehv №2: I/O ve JSON-u bir setirlik “qatiq”a qatmaq, sonra da harada yixildigini anlamamaq.
Eger siz bir setirde Json.decodeFromString(File("x").readText()) edirsinizse, xeta olanda istisnani gorursunuz, amma problemin fayli oxumada oldugunu yoxsa parsing-də oldugunu başa dusmursunuz. Addimlari ayri saxlayanda ise debug demek olar ki, darixdirici olur: “fayl oxundu — setr buradadir — parsing yixildi — demek JSON modelə uygun deyil”.

Sehv №3: yazmadan once direktoriyani yaratmamaq.
File("data/expenses.json").writeText(...) gozel gorunur, amma eger data qovlugu yoxdursa, yazma bas tutmaya biler. Buna gore yeni baslayanlar bezen “serializasiya islemir” dusunurler, halbuki serializasiya isleyib, yazma yixilib. Direktoriyani mkdirs() ile yaradın (ve ya saxlamanin daxilinde file.parentFile?.mkdirs() ile).

Sehv №4: yuxari səviyyeni sehv dekod etmek: {} yerinə [].
Eger siz xerc siyahisi saxlayirsinizsa, faylda JSON massiv olacaq [...]. Sonra sehvən bunu bir Expense kimi dekod etmeye calişsaniz, istisna alacaqsiniz. En sade ozunu yoxlama usulu — JSON-un ilk simvoluna baxmaqdir: { obyekt demekdir, [ siyahi demekdir.

Sehv №5: faylin olmamasini xeta saymaq.
Proqramin ilk işə düşmesi demek olar ki, her zaman faylsiz olur. Eger burada istisna ile yixilirsinizse, siz həyati baslamağı bacarmayan bir tetbiq edirsiniz. “Fayl yoxdur” vəziyyətini “hele melumat yoxdur” kimi qebul edin ve emptyList() qaytarin — bu, istifadəçiyə (ve size, test edende) daha dostdur.

Sehv №6: “JSON parse oldusa — demek melumatlar duzgundur” dusunmek.
Parsing struktur ve tipləri yoxlayir, amma menani yox. Meselen, amount = -999999 tamamilə parse ola biler. Buna gore yadda saxlamaq faydalidir: serializasiya/deserializasiya — bu “melumatin formasi” qatidir, menanin duzgunluyunu yoxlama ise tetbiqinizin ayrıca məntiqidir. Bu gun biz sadəcə bu serhedi qeyd edirik ki, JSON-dan mumkunsuzu teleb etmeyek.

1
Tapşırıq
Kotlin SELF, səviyyə, dərs
Bağlanıb
Profilin ehtiyat nüsxəsi
Profilin ehtiyat nüsxəsi
1
Tapşırıq
Kotlin SELF, səviyyə, dərs
Bağlanıb
Tapşırıqların sinxronlaşdırılması
Tapşırıqların sinxronlaşdırılması
1
Tapşırıq
Kotlin SELF, səviyyə, dərs
Bağlanıb
Ayarlar faylı
Ayarlar faylı
1
Tapşırıq
Kotlin SELF, səviyyə, dərs
Bağlanıb
Kitab rəfi
Kitab rəfi
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION