Recentemente atopei un reto interesante que implica a decodificación de JSON en Swift. Como moitos desenvolvedores, cando me enfrontei a unha resposta grande e complexa de JSON, o meu primeiro instinto era chegar a ferramentas de "fixión rápida". , varios conversores JSON-to-Swift, e mesmo modelos modernos de IA - manexarían unha estrutura de datos confusa e repetitiva. Quicktype Para ser sincero, estiven completamente decepcionado. O problema: O pesadelo "Flat" de JSON O problema xorde cando se atopa unha API herdada ou unha resposta mal estruturada que usa propiedades numeradas "flat" en lugar de arreglos limpos. { "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 } ] } Por que os conversores en liña fallan Cando conectei isto ás ferramentas de conversión estándar, o resultado foi un pesadelo de mantemento. Xeraron un "muro de propiedades" que parecía algo así: 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? } Imos ser honestos, o código xerado por esas ferramentas en liña pertence ao "barril de lixo" para calquera proxecto serio. Non só é inescalable, senón que imaxina a mirada na cara do seu desenvolvedor senior durante unha revisión de relacións públicas cando ven máis de 40 propiedades opcionais. Decidín tomar o control do proceso de decodificación para facelo limpo, Swifty, e - o máis importante - Aquí está como estruturei a solución e por que funciona. production-ready A arma secreta: por que usamos unha estrutura para codificar No 99% dos tutoriais de Swift, podes ver Definido como un Os enumes son excelentes cando coñeces cada unha das claves no momento da compilación.Pero no noso caso, temos un JSON "plano" con claves como , ata arriba Escribir un enum con 40 casos non é só aburrido - é mala enxeñaría. No seu lugar. CodingKeys enum strIngredient1 strIngredient2 20 struct A violación dos requisitos do protocolo de conformidade coa Un home debe manexar os dous e Por exemplo, a través de estruturas, podemos pasar Introduza o inicializador no runtime. CodingKey String Int calquera 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 } Mapear as teclas "ugly" para limpar os nomes Non tes que seguir as convencións de nomeamento da API dentro da túa aplicación. Isto mantén o resto da lóxica de decodificación lexible mentres mantén as teclas da API "sucias" illadas dentro desta estrutura. static var static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") O poder da xeración dinámica de chaves Esta é a parte que fai que este enfoque sexa superior a calquera código xerado por IA. Creamos funcións estáticas que usan para xerar chaves no voo. string interpolation static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } En lugar de codificar , , etc., agora temos unha "fábrica chave". Cando loop a través No noso inicializador, simplemente chamamos estas funcións. É limpo, é reutilizable e é significativamente máis difícil facer un tipo que escribir 40 casos individuais. strIngredient1 strIngredient2 1...20 Construír un modelo que realmente teña sentido O JSON orixinal trata un ingrediente e a súa medición como dous estraños que viven en casas diferentes. Na nosa aplicación, hai un par. struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } Por que e o ? Hashable id Hashable id Engadín unha Propiedade usando o índice de loop. Por que? Porque as vistas modernas de SwiftUI son como e requirir datos identificables. Así que garantimos: id List ForEach Hashable SwiftUI non se confundirá se dous ingredientes diferentes teñen o mesmo nome (como dous tipos diferentes de "Salt"). Desempeño: Fontes de datos difables adoran obxectos hashables. Limpar o “olor de API” Antes de chegar ao inicializador, mira como definimos as nosas propiedades principais.Non estamos só a copiar o que a API nos dá; estamos a traducilo en . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Adeus str Prefixo: Abandonamos a notación húngara. nome é mellor que strMeal. Se a API envía un enlace roto ou unha cadea baleira, o noso decodificador xestiona graciosamente durante a fase de análise, non máis tarde na vista. O inicializador intelixente: o noso “bouncer de datos” En lugar de aceptar cegamente cada clave que ofrece JSON, o noso personalizado actúa como un rexeitador nun club - só os datos válidos entran. 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 } O resultado final: limpo, rápido e preparado para a interface Despois de todo o que funciona detrás das escenas, mira o que logramos. Transformamos un pesadelo "plano" de JSON nun modelo que é un pracer de usar. struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Simplicidade pura na UI Porque fixemos o levantamento pesado durante a fase de decodificación - filtrando os valores baleiros e agrupando os ingredientes - o noso código SwiftUI faise incrible limpo. A cereixa no cumio: Facer mocking fácil Pode ter notado un pequeno efecto secundario: cando definimos unha costume , Swift deixa de xerar o inicializador de membro por defecto. Isto pode facer que as probas de unidade de escritura ou as previsións de SwiftUI sexan un pouco molestas. init(from: Decoder) Para corrixir isto e manter a nosa base de código "amigable á proba", podemos engadir esta simple extensión. Isto permítenos crear datos "Mock" para a nosa interface sen necesitar un ficheiro 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 } } Agora, crear unha previsión é tan sinxelo como: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) Conclusión A próxima vez que se enfronte a unha API confusa, lembre: As ferramentas en liña e a IA poden darche unha solución rápida de “copia-pasta”, pero a miúdo levan a débedas técnicas. Para a súa implementación, creas un código que é: don’t let the backend dictate your frontend architecture. Decodable Lexible: Nomes de propiedades claros e baseados na intención. Robusto: filtra os datos baleiros ou corrompidos na fonte. Manter: Fácil de probar e fácil de amosar na UI. Feliz codificación, e manter os seus modelos limpos! O código completo está aquí: https://github.com/PavelAndreev13/NetworkLayer