Saya baru-baru ini menghadapi cabaran yang menarik yang melibatkan dekoding JSON dalam Swift. Seperti kebanyakan pengembang, apabila dihadapkan dengan respons JSON yang besar dan kompleks, naluri pertama saya ialah untuk menjangkau alat "perbaikan cepat". , pelbagai penukar JSON-to-Swift, dan juga model AI moden - akan menangani struktur data yang berulang-ulang. Quicktype Untuk jujur, saya benar-benar tertekan. Masalah: The “Flat” JSON Mimpi Mimpi Buruk Masalah ini timbul apabila anda menghadapi API lama atau tindak balas terstruktur yang buruk yang menggunakan sifat bernomor "flat" alih-alih rangkaian bersih. { "meals": [ { "idMeal": "52771", "strMeal": "Spicy Arrabiata Penne", "strInstructions": "Bring a large pot of water to a boil...", "strMealThumb": "https://www.themealdb.com/images/media/meals/ustsqw1468250014.jpg", "strIngredient1": "penne rigate", "strIngredient2": "olive oil", "strIngredient3": "garlic", "strIngredient4": "chopped tomatoes", "strIngredient5": "red chilli flakes", // ... this continues up to strIngredient20 "strMeasure1": "1 pound", "strMeasure2": "1/4 cup", "strMeasure3": "3 cloves", // ... this continues up to strMeasure20 } ] } Mengapa Penukar Online Gagal Apabila saya menyambungkan ini ke dalam alat penukaran standard, hasilnya ialah mimpi buruk pemeliharaan. Mereka menghasilkan “dinding sifat” yang kelihatan seperti ini: struct Meal: Codable { let idMeal: String let strMeal: String let strInstructions: String? let strMealThumb: String? // The repetitive property nightmare let strIngredient1: String? let strIngredient2: String? let strIngredient3: String? // ... let strIngredient20: String? let strMeasure1: String? let strMeasure2: String? let strMeasure3: String? // ... let strMeasure20: String? } Marilah kita jujur, kod yang dihasilkan oleh alat-alat dalam talian ini termasuk dalam "batang sampah" untuk mana-mana projek yang serius.Bukan sahaja ia tidak boleh diukur, tetapi bayangkan pandangan di muka pengembang senior anda semasa tinjauan PR apabila mereka melihat lebih daripada 40 properti opsional.Ia adalah mimpi buruk pemeliharaan dan pukulan kepada reputasi profesional anda. Saya memutuskan untuk mengambil kawalan proses decoding untuk menjadikannya bersih, Swifty, dan - yang paling penting - Berikut ialah bagaimana saya menyusun penyelesaian dan mengapa ia berfungsi. production-ready Senjata Rahsia: Mengapa Kita Gunakan Struktur Untuk KodingKeys Dalam 99% tutorial Swift, anda boleh melihat ditakrifkan sebagai sebuah Enums adalah hebat apabila anda tahu setiap kunci tunggal pada masa kompilasi. tetapi dalam kes kami, kami mempunyai JSON "tempat" dengan kunci seperti daripada sehingga ke Menulis enum dengan 40 kes bukan sahaja membosankan - ia adalah kejuruteraan yang buruk. Sebaliknya CodingKeys enum strIngredient1 strIngredient2 20 struct 1. melanggar keperluan protokol untuk mematuhi Seorang lelaki mesti mengendalikan kedua-dua dan Dengan menggunakan struktur, kita boleh melepasi Masukkan ini ke dalam initializer pada runtime. CodingKey String Int mana-mana struct CodingKeys: CodingKey { let stringValue: String var intValue: Int? init?(stringValue: String) { self.stringValue = stringValue } // This allows us to map any raw string from the JSON to our logic init(rawValue: String) { self.stringValue = rawValue } init?(intValue: Int) { return nil } // We don't need integer keys here } Mapping “Ugly” keys untuk nama bersih Anda tidak perlu berpegang kepada konvensi penamaan API di dalam aplikasi anda. Ini mengekalkan logik dekoding yang lain boleh dibaca sambil mengekalkan kunci API " kotor" yang terisolasi di dalam struktur ini. static var static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") Kuasa Generasi Kunci Dinamis Ini adalah bahagian yang menjadikan pendekatan ini lebih unggul daripada mana-mana kod yang dihasilkan oleh AI. untuk menghasilkan kunci pada pesawat. string interpolation static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } Daripada Hardcoding daripada , dan lain-lain, kita kini mempunyai "pabrik kunci." Apabila kita loop melalui Ia bersih, ia boleh digunakan semula, dan ia lebih sukar untuk membuat typpo daripada menulis 40 kes individu. strIngredient1 strIngredient2 1...20 Membina model yang sebenarnya bermakna JSON asal memperlakukan bahan dan pengukurannya seperti dua orang asing yang tinggal di rumah yang berbeza. Dalam aplikasi kami, terdapat sepasang. struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } kenapa dan yang ? Hashable id Tag: Hashable id Saya menambah satu Perbezaan antara SwiftUI dengan SwiftUI ialah: Mengapa? dan memerlukan data yang boleh diidentifikasi. dengan mematuhi Kami menjamin : id List ForEach Hashable Tiada kesilapan UI: SwiftUI tidak akan bingung jika dua bahan yang berbeza mempunyai nama yang sama (seperti dua jenis "Salt" yang berbeza). Prestasi: Sumber data difabel suka objek hashable. Membersihkan “bau api” Sebelum kita sampai ke initializer, lihat bagaimana kita mendefinisikan sifat-sifat utama kita.Kami bukan sahaja menyalin apa yang API berikan kepada kita; kami menerjemahkannya kepada . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Goodbye str Prefix: Kami membuang notasi Hungarian. nama lebih baik daripada strMeal. Jenis yang betul: Kami mendekodkan gambar kecil secara langsung ke dalam URL?.Jika API menghantar pautan pecah atau string kosong, decoder kami mengendalikannya dengan sopan semasa fasa penyelidikan, tidak kemudian dalam pandangan. The Smart Initializer: “Data Bouncer” kami Daripada buta menerima setiap kunci yang ditawarkan oleh JSON, kami bertindak seperti bouncer di sebuah kelab - hanya data yang sah masuk. init(from:) init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 1. Decode simple properties using our clean aliases self.name = try container.decode(String.self, forKey: .name) self.thumb = try? container.decode(URL.self, forKey: .thumb) self.instructions = try container.decode(String.self, forKey: .instructions) // 2. The Dynamic Decoding Loop var ingredients: [Ingredient] = [] for index in 1...20 { // We use 'try?' because some keys might be null or missing if let name = try? container.decode(String.self, forKey: .strIngredient(index)), let measure = try? container.decode(String.self, forKey: .strMeasure(index)), !name.isEmpty, !measure.isEmpty { // We only save it if the name AND measure are valid and non-empty ingredients.append(Ingredient(id: index, name: name, measure: measure)) } } self.ingredients = ingredients } Hasil Akhir: Bersih, Swifty, dan Bersedia UI Selepas semua yang bekerja di belakang adegan, lihat apa yang telah kami capai. kami telah mengubah mimpi buruk JSON yang "dalam" menjadi model yang menyenangkan untuk digunakan. struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Kesederhanaan dalam UI Kerana kami melakukan pengangkat yang berat semasa fasa dekoding - menapis nilai kosong dan mengumpulkan bahan-bahan - kod SwiftUI kami menjadi sangat bersih. The Cherry On Top: Membuat Mocking Mudah Anda mungkin telah memperhatikan satu kesan sampingan kecil: apabila kita mendefinisikan kebiasaan , Swift menghentikan pengeluaran initializer anggota lalai. Ini boleh menjadikan ujian unit penulisan atau SwiftUI Previews sedikit menjengkelkan. init(from: Decoder) Untuk membetulkan ini dan mengekalkan kod pangkalan kami "penguji mesra", kami boleh menambah ekstensi mudah ini. Ini membolehkan kami mencipta data "Mock" untuk UI kami tanpa memerlukan fail JSON. extension MealDetail { // Restoring the ability to create manual instances for Mocks and Tests init(name: String, thumb: URL?, instructions: String, ingredients: [Ingredient]) { self.name = name self.thumb = thumb self.instructions = instructions self.ingredients = ingredients } } Sekarang, mencipta preview mudah seperti: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) Kesimpulan Kali seterusnya anda menghadapi API yang kacau, ingat: Alat-alat dalam talian dan AI boleh memberi anda penyelesaian "copy-paste" yang cepat, tetapi mereka sering membawa kepada hutang teknikal. Dalam pelaksanaan, anda mencipta kod yang ialah: don’t let the backend dictate your frontend architecture. Decodable Boleh dibaca: Nama hartanah yang jelas, berdasarkan niat. Robust: Menyaring data kosong atau rosak di sumber. Mudah untuk diuji dan mudah untuk dipaparkan dalam UI. Happy koding, dan simpan model anda bersih! Kode lengkap di sini: https://github.com/PavelAndreev13/NetworkLayer