Ik kwam onlangs voor een interessante uitdaging met betrekking tot JSON-decodering in Swift. Net als veel ontwikkelaars, toen ik geconfronteerd werd met een grote, complexe JSON-respons, was mijn eerste instinct om te reiken naar "snelle fix" -tools. , verschillende JSON-to-Swift-converters, en zelfs moderne AI-modellen - zouden een rommelige, repetitieve gegevensstructuur beheren. Quicktype Om eerlijk te zijn, was ik volledig ondergedompeld. Het probleem: de "flat" JSON nachtmerrie Het probleem treedt op wanneer u een ouderwetse API of een slecht gestructureerde reactie tegenkomt die "flat" genummerde eigenschappen gebruikt in plaats van schone array's. { "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 } ] } Waarom online converters falen Toen ik deze plug in de standaard conversie tools, het resultaat was een onderhoud nachtmerrie. 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? } Laten we eerlijk zijn, de code gegenereerd door die online hulpmiddelen behoort tot de "rash bin" voor elk serieus project. Niet alleen is het onschatbaar, maar stel je de blik op het gezicht van je senior ontwikkelaar voor tijdens een PR-beoordeling wanneer ze 40+ optionele eigenschappen zien. Ik besloot om de controle over het decoderingsproces te nemen om het schoon te maken, Swifty, en - het belangrijkste - Hier is hoe ik de oplossing gestructureerd en waarom het werkt. production-ready Het geheime wapen: waarom we een structuur gebruiken voor coderingkeys In 99% van de Swift tutorials zie je gedefinieerd als een Enums zijn geweldig als je elke enkele sleutel op compilatietijd kent.Maar in ons geval hebben we een "flat" JSON met sleutels zoals - het ... tot op Het schrijven van een enum met 40 gevallen is niet alleen saai – het is slechte engineering. In plaats daarvan. CodingKeys enum strIngredient1 strIngredient2 20 struct Het overtreden van de vereisten van het protocol In overeenstemming met Een man moet beide aanpakken en met behulp van een struct, kunnen we passeren String in de initializer bij runtime. CodingKey String Int Iedere 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” sleutels naar schone namen Je hoeft niet vast te houden aan de API-namingconventies in je app. Dit houdt de rest van de decodering logica leesbaar terwijl de "vuile" API-toets geïsoleerd in deze structuur. static var static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") De kracht van dynamische sleutelgeneratie Dit is het onderdeel dat deze aanpak superieur maakt aan elke door AI gegenereerde code. om sleutels op de vlieg te genereren. string interpolation static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } In plaats van hardcoding - het , enzovoort, hebben we nu een "sleutelfabriek". Het is schoon, het is herbruikbaar, en het is aanzienlijk moeilijker om een typo te maken dan het schrijven van 40 individuele gevallen. strIngredient1 strIngredient2 1...20 Het bouwen van een model dat eigenlijk zinvol is De originele JSON behandelt een ingrediënt en de meting ervan als twee vreemden die in verschillende huizen wonen. In onze app zijn er een paar. struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } Waarom En de ? Hashig id Hashig id Ik voegde een eigenschappen met behulp van de loopindex. Waarom? Omdat moderne SwiftUI-weergaven zoals en vereist identificerbare gegevens. door te voldoen aan Wij zorgen voor: id List ForEach Hashable Geen UI-glitches: SwiftUI wordt niet verward als twee verschillende ingrediënten dezelfde naam hebben (zoals twee verschillende soorten "Salt"). Prestaties: Diffabele gegevensbronnen houden van hashbare objecten. Het opruimen van de “API-geur” Voordat we naar de initializer gaan, bekijken we hoe we onze belangrijkste eigenschappen definiëren.We kopiëren niet alleen wat de API ons geeft; we vertalen het in . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Goodbye str Prefix: We hebben de Hongaarse notatie laten vallen. naam is beter dan strMeal. Als de API een gebroken link of een lege string stuurt, verwerkt onze decoder deze gracieus tijdens de analysefase, niet later in de weergave. De Smart Initializer: onze “Data Bouncer” In plaats van blindelings elke sleutel die JSON biedt te accepteren, fungeert als een bouncer bij een club - alleen geldige gegevens komen binnen. 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 } Het eindresultaat: Clean, Swifty en UI-ready Na al dat werk achter de schermen, bekijk wat we hebben bereikt.We hebben een "flat" JSON nachtmerrie omgezet in een model dat een plezier is om te gebruiken.Dit is wat de rest van uw app nu ziet: struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Pure eenvoud in de UI Omdat we de zware lifting tijdens de decoderingsfase hebben gedaan - het filteren van lege waarden en het groeperen van ingrediënten - wordt onze SwiftUI-code ongelooflijk schoon. De Kers op de top: Making Mocking Easy Je hebt misschien één klein bijwerkingen opgemerkt: wanneer we een gewoonte definiëren , Swift stopt met het genereren van de standaard memberwise initializer. Dit kan het schrijven van eenheidstests of SwiftUI Previews een beetje vervelend maken. init(from: Decoder) Om dit op te lossen en onze codebase "testvriendelijk" te houden, kunnen we deze eenvoudige extensie toevoegen. 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 } } Nu, het maken van een preview is zo eenvoudig als: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) Conclusie De volgende keer dat je geconfronteerd wordt met een rommelige API, onthoud: Online tools en AI kunnen u een snelle “copy-paste” oplossing geven, maar ze leiden vaak tot technische schulden. In de implementatie maakt u een code die is: don’t let the backend dictate your frontend architecture. Decodable Leesbaar: duidelijke, op intentie gebaseerde eigendomsnamen. Robuust: Filtert lege of corrupte gegevens uit bij de bron. Onderhoudbaar: Makkelijk te testen en gemakkelijk te weergeven in de UI. Goede codering, en houd uw modellen schoon! De volledige code is hier: https://github.com/PavelAndreev13/NetworkLayer