Nedávno jsem narazil na zajímavou výzvu zahrnující dekódování JSON ve Swift. Stejně jako mnoho vývojářů, když jsem čelil velké, složité odpovědi JSON, můj první instinkt byl dosáhnout nástrojů „rychlé opravy“. , různé konvertory JSON-to-Swift, a dokonce i moderní modely umělé inteligence – by se vypořádaly s chaotickou, opakující se datovou strukturou. Quicktype Abych byl upřímný, byl jsem naprosto zklamaný. The Problem: The “Flat” JSON Noční můra Problém nastává, když narazíte na starší rozhraní API nebo špatně strukturovanou odpověď, která používá „plotně“ očíslované vlastnosti namísto čistých řad. { "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 } ] } Proč online konvertory selhávají Když jsem to připojil ke standardním nástrojům pro konverzi, výsledkem byla noční můra údržby. Vytvořili „stěnu vlastností“, která vypadala něco takového: 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? } Buďme upřímní, kód generovaný těmito on-line nástroji patří do „odpadkového koše“ pro jakýkoli vážný projekt. Nejen, že je to neúměrné, ale představte si, jak vypadá tvář vašeho vedoucího vývojáře během přezkumu PR, když vidí více než 40 volitelných vlastností. Rozhodl jsem se převzít kontrolu nad procesem dešifrování, aby byl čistý, rychlý a – co je nejdůležitější – Zde je, jak jsem strukturoval řešení a proč to funguje. production-ready Tajná zbraň: Proč používáme strukturu pro kódovací klíče V 99% Swift tutoriálů vidíte Definováno jako Enums jsou skvělé, když znáte každý jednotlivý klíč v době kompilace. ale v našem případě máme "plotový" JSON s klíči jako , , ... až do Napsat enum s 40 případy není jen nudné – je to špatné inženýrství. Místo toho CodingKeys enum strIngredient1 strIngredient2 20 struct Porušení požadavků protokolu V souladu s Muž se musí vypořádat s oběma a Použitím struktury můžeme projít Přejděte do inicializátoru v runtime. CodingKey String Int jakýkoliv 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 } Mapování „špatných“ klíčů na čistá jména Nemusíte se držet pojmenovacích konvencí API uvnitř vaší aplikace. To udržuje zbytek dekódovací logiky čitelnou a zároveň udržuje "špinavé" API klíče izolované uvnitř této struktury. static var static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") Síla dynamické generace klíčů To je část, která činí tento přístup nadřazeným jakémukoli kódu generovanému AI. Vytvořili jsme statické funkce, které používají generovat klíče na letu. string interpolation static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } Namísto hardcoveru , , a tak dále, máme nyní „klíčovou továrnu“. Je čistý, je opakovatelný a je výrazně obtížnější psát typ než psát 40 jednotlivých případů. strIngredient1 strIngredient2 1...20 Vytvořit model, který skutečně dává smysl Původní JSON zachází se složkou a jejím měřením jako se dvěma cizinci žijícími v různých domech. V naší aplikaci existuje pár. struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } Proč A ten ? Hashable id Hashable id Přidal jsem jeden Proč?Protože moderní SwiftUI zobrazuje jako a vyžadují identifikační údaje. Zajišťujeme tedy: id List ForEach Hashable Žádné překážky uživatelského rozhraní: SwiftUI se nezamění, pokud mají dvě různé složky stejný název (jako dva různé typy „Salt“). Výkon: Rozptýlené zdroje dat milují hashovatelné objekty. Čištění „API zápachu“ Než se dostaneme k inicializátoru, podívejte se, jak definujeme naše hlavní vlastnosti. Nejsme jen kopírování toho, co nám API dává; překládáme to do . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Sbohem str Prefix: My jsme spustili maďarské označení. jméno je lepší než strMeal. Správné typy: Rozluštíme miniaturní obrázek přímo do adresy URL?.Pokud API odesílá zlomený odkaz nebo prázdný řetězec, náš dekodér se s ním chytře vypořádá během fáze analýzy, nejpozději v zobrazení. Inteligentní inicializátor: náš „Data Bouncer“ Místo toho, abychom slepě přijali každý klíč, který JSON nabízí, naše vlastnosti Působí jako bouncer v klubu - vstupují pouze platná data. 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 } Konečný výsledek: čistý, rychlý a připravený pro uživatele Po tom všem, co funguje za scénami, se podívejte na to, co jsme dosáhli. Přeměnili jsme „plochou“ JSON noční můru na model, který je radostí používat. struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Čistá jednoduchost v UI Protože jsme během dekódovací fáze prováděli těžké zvedání – filtrování prázdných hodnot a seskupování složek – náš kód SwiftUI se stává neuvěřitelně čistým. Cherry na vrcholu: Making Mocking Easy Možná jste si všimli jednoho malého vedlejšího účinku: když definujeme zvyk Swift přestane vytvářet výchozí členský inicializátor, což může způsobit, že psaní testů jednotky nebo SwiftUI Previews bude trochu nepříjemné. init(from: Decoder) Abychom to napravili a udrželi naši kódovou základnu „testovací přívětivou“, můžeme přidat toto jednoduché rozšíření.To nám umožňuje vytvářet data „Mock“ pro naše uživatelské rozhraní, aniž bychom potřebovali soubor 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 } } Nyní je vytvoření náhledu tak jednoduché jako: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) Závěr Příště, když se setkáte s nepořádným API, nezapomeňte: Online nástroje a umělá inteligence vám mohou poskytnout rychlé „kopírování“ řešení, ale často vedou k technickému dluhu. Při implementaci vytvoříte kód, který je: don’t let the backend dictate your frontend architecture. Decodable Čitelné: Jasné názvy nemovitostí založené na záměru. Robustní: Filtruje prázdná nebo poškozená data u zdroje. Udržitelné: Snadné testování a snadné zobrazení v rozhraní uživatele. Šťastné kódování a udržujte vaše modely čisté! Kompletní kód je zde: https://github.com/PavelAndreev13/NetworkLayer