Pembuat JSOND generik

Saat ini, sebagian besar aplikasi seluler adalah client-server. Di mana-mana ada pemuatan, sinkronisasi, pengiriman acara dan cara utama untuk berinteraksi dengan server adalah bertukar data menggunakan format json.


Penguraian kode kunci


Foundation memiliki dua mekanisme untuk serialisasi-de-dead data. Lama adalah NSJsonSerialization dan baru adalah Codable . Yang terakhir dalam daftar keunggulan memiliki hal yang luar biasa di dalamnya seperti pembuatan kunci otomatis untuk data json berdasarkan pada struktur (atau kelas) yang mengimplementasikan Codable ( Encodable , Decodable ) dan inisialisasi untuk mendekode data.


Dan semuanya tampak baik-baik saja, Anda dapat menggunakan dan menikmati, tetapi kenyataannya tidak begitu sederhana.
Cukup sering di server Anda dapat melihat json dari formulir:


 {"topLevelObject": { "underlyingObject": 1 }, "Error": { "ErrorCode": 400, "ErrorDescription": "SomeDescription" } } 

Ini adalah contoh kehidupan nyata dari salah satu server proyek.


Untuk kelas JsonDecoder , JsonDecoder dapat menentukan pekerjaan dengan kunci snake_case, tetapi bagaimana jika kita memiliki UpperCamelCase, dash-snake-case, atau bahkan gado-gado umum, tetapi kami tidak ingin menulis kunci secara manual?


Untungnya, Apple menyediakan kemampuan untuk mengkonfigurasi pemetaan kunci sebelum mencocokkannya dengan struktur JSONDecoder.KeyDecodingStrategy menggunakan JSONDecoder.KeyDecodingStrategy . Kami akan mengambil keuntungan dari ini.


Pertama, buat struktur yang mengimplementasikan protokol CodingKey , karena tidak ada di perpustakaan standar:


  struct AnyCodingKey: CodingKey { var stringValue: String var intValue: Int? init(_ base: CodingKey) { self.init(stringValue: base.stringValue, intValue: base.intValue) } init(stringValue: String) { self.stringValue = stringValue } init(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } init(stringValue: String, intValue: Int?) { self.stringValue = stringValue self.intValue = intValue } } 

Maka perlu untuk secara terpisah memproses setiap kasus kunci kami. Yang utama:
snake_case, dash-snake-case, lowerCamelCase dan UpperCamelCase. Periksa, jalankan, semuanya berfungsi.


Kemudian kita menemukan masalah yang agak diharapkan: singkatan di camelCase'ah (ingat banyak id , Id , ID ). Untuk membuatnya berfungsi, Anda perlu mengubahnya dengan benar dan memperkenalkan aturan - singkatan dikonversi ke camelCase, hanya menyimpan huruf kapital pertama dan myABBRKey akan berubah menjadi myAbbrKey .


Solusi ini sangat cocok untuk kombinasi beberapa kasus.


Catatan: Implementasi akan dimasukkan ke dalam. Strategi decoding kunci .custom .


 static func convertToProperLowerCamelCase(keys: [CodingKey]) -> CodingKey { guard let last = keys.last else { assertionFailure() return AnyCodingKey(stringValue: "") } if let fromUpper = convertFromUpperCamelCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromUpper) } else if let fromSnake = convertFromSnakeCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromSnake) } else { return AnyCodingKey(last) } } 

Decoding tanggal


Masalah rutin berikutnya adalah cara tanggal dilewati. Ada banyak layanan microser di server, ada sedikit perintah yang lebih sedikit, tetapi juga jumlah yang layak, dan sebagai hasilnya kita dihadapkan dengan banyak format tanggal seperti "Ya, saya menggunakan standar". Selain itu, seseorang melewati tanggal dalam string, seseorang di zaman Zaman. Sebagai hasilnya, kami sekali lagi memiliki gado-gado kombinasi string-number-timezone-millisecond-separator, dan DateDecoder di iOS mengeluh dan memerlukan format tanggal yang ketat. Solusinya di sini sederhana, hanya dengan mencari kita mencari tanda-tanda format tertentu dan menggabungkannya, mendapatkan hasil yang diperlukan. Format ini berhasil dan sepenuhnya mencakup kasus saya.


Catatan: Ini adalah penginisialisasi DateFormatter khusus. Hanya mengatur format ke formatter yang dibuat.


 static let onlyDate = DateFormatter(format: "yyyy-MM-dd") static let full = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSSx") static let noWMS = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ssZ") static let noWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSS") static let noWMSnoWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss") 

Kami melampirkan ini ke decoder kami menggunakan JSONDecoder.DateDecodingStrategy dan mendapatkan decoder yang memproses hampir semua hal dan mengubahnya menjadi format yang dapat dicerna bagi kami.


Tes kinerja


Pengujian dilakukan untuk string json ukuran 7944 byte.


strategi convertFromSnakeCasestrategi anyCodingKey
Mutlak0,001700,00210
Relatif81%100%

Seperti yang dapat kita lihat, Decoder kustom 20% lebih lambat karena verifikasi wajib dari setiap kunci di json untuk kebutuhan untuk mengubah. Namun, ini adalah biaya kecil karena tidak harus secara eksplisit mendaftarkan kunci untuk struktur data dengan mengimplementasikan Codable . Jumlah plat boiler sangat berkurang dalam proyek dengan penambahan decoder ini. Haruskah saya menggunakannya untuk menghemat waktu pengembang, tetapi memperburuk kinerja? Terserah kamu.


Kode sampel lengkap di perpustakaan github


Artikel bahasa inggris

Source: https://habr.com/ru/post/id449870/


All Articles