Recientemente me encontré con un reto interesante que involucra el descifrado de JSON en Swift. Al igual que muchos desarrolladores, cuando me enfrento a una gran y compleja respuesta de JSON, mi primer instinto fue llegar a herramientas de "rectificación rápida". , varios convertidores JSON-to-Swift, e incluso modelos modernos de IA - tratarían una estructura de datos confusa y repetitiva. Quicktype Para ser honesto, me quedé completamente enojado. El problema: La pesadilla “plata” de JSON El problema surge cuando se encuentra con una API heredada o una respuesta mal estructurada que utiliza propiedades numeradas "flat" en lugar de agrupaciones limpias. { "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 qué fallan los conversores en línea Cuando conecté esto a las herramientas de conversión estándar, el resultado fue una pesadilla de mantenimiento. Generaron 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? } Vamos a ser honestos, el código generado por esas herramientas en línea pertenece a la "barrera de basura" para cualquier proyecto serio. No sólo es inescalable, sino que imagina la mirada en la cara de su desarrollador senior durante una revisión de relaciones públicas cuando ven 40 más propiedades opcionales. Decidí tomar el control del proceso de descifrado para que fuera limpio, rápido y, lo más importante, Aquí está cómo estructuré la solución y por qué funciona. production-ready La arma secreta: por qué usamos una estructura para codificar En el 99% de los tutoriales de Swift, puedes ver Definido como un Los enums son buenos cuando conoces cada clave en el momento de compilar, pero en nuestro caso, tenemos un JSON "plano" con claves como , de ... hasta Escribir un enum con 40 casos no es sólo aburrido, es mala ingeniería. en su lugar. CodingKeys enum strIngredient1 strIngredient2 20 struct 1.- La violación de los requisitos del protocolo De conformidad con Un hombre debe lidiar con los dos y Con el uso de una estructura, podemos pasar Introducción al inicializador en el runtime. CodingKey String Int cualquier 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 las claves “ugly” para limpiar los nombres No tiene que adherirse a las convenciones de nombramiento de la API dentro de su aplicación. Esto mantiene el resto de la lógica de decodificación legible mientras mantiene las claves de API "sucias" aisladas dentro de esta estructura. static var static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") El poder de la generación de llaves dinámicas Esta es la parte que hace que este enfoque sea superior a cualquier código generado por IA. Hemos creado funciones estáticas que utilizan Para generar llaves en el vuelo. 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 , de , etc., ahora tenemos una "fábrica clave". En nuestro inicializador, simplemente llamamos estas funciones. es limpio, es reutilizable, y es significativamente más difícil hacer un tipo que escribir 40 casos individuales. strIngredient1 strIngredient2 1...20 Construir un modelo que realmente tenga sentido El original JSON trata a un ingrediente y su medición como dos extraños que viven en casas diferentes. En nuestra aplicación, hay un par. Al niñar una estructura dedicada, fijamos la arquitectura de datos en la fuente: struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } por qué Y el ? Hashable id Hashable id He añadido un Propiedad utilizando el índice de loop. ¿Por qué? Porque las vistas modernas de SwiftUI son como y requerir datos identificables. por conformidad con Nosotros nos aseguramos: id List ForEach Hashable No hay problemas de interfaz de usuario: SwiftUI no se confundirá si dos ingredientes diferentes tienen el mismo nombre (como dos tipos diferentes de “Salt”). Desempeño: Las fuentes de datos difables aman los objetos hashables. Limpiar el “olor de API” Antes de llegar al inicializador, vea cómo definimos nuestras propiedades principales.No estamos simplemente copiando lo que la API nos da; estamos traduciéndolo en . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Adiós str Prefixo: Hemos dejado caer la notación húngara. nombre es mejor que strMeal. Tipos adecuados: Decodificamos la miniatura directamente en una URL. Si la API envía un enlace roto o una cadena vacía, nuestro decodificador lo manejará graciosamente durante la fase de análisis, no más tarde en la vista. El inicializador inteligente: nuestro “bouncer de datos” En lugar de aceptar ciegamente cada clave que ofrece JSON, nuestro actúa como un bouncer en un club: solo entran datos válidos. 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 } El resultado final: limpio, rápido y listo para la interfaz Después de todo lo que funciona detrás de las escenas, vea lo que hemos logrado. Hemos transformado una pesadilla JSON "plata" en un modelo que es un placer de usar. struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Simplicidad en la UI Debido a que hicimos el duro levantamiento durante la fase de decodificación - filtrando los valores vacíos y agrupando los ingredientes - nuestro código SwiftUI se vuelve increíblemente limpio. La cereza en la parte superior: hacer que la burla sea fácil Puede haber notado un pequeño efecto secundario: cuando definimos una costumbre , Swift deja de generar el inicializador de membresía predeterminado. Esto puede hacer que las pruebas de unidad de escritura o SwiftUI Previews sean un poco molestas. init(from: Decoder) Para corregir esto y mantener nuestra base de código "test-friendly", podemos añadir esta extensión simple. Esto nos permite crear datos "Mock" para nuestra interfaz de usuario sin necesidad de un archivo 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 } } Ahora, crear una vista previa es tan simple como: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) Conclusión La próxima vez que se enfrente a una API desordenada, recuerde: Las herramientas en línea y la IA pueden darle una solución rápida de “copia-pasta”, pero a menudo conducen a la deuda técnica. Después de la implementación, se crea un código que es: don’t let the backend dictate your frontend architecture. Decodable LECTABLE: Nombres de propiedad claros y basados en la intención. Robusto: filtra los datos vacíos o corruptos en la fuente. Mantenimiento: Fácil de probar y fácil de mostrar en la interfaz de usuario. ¡Feliz codificación, y mantenga sus modelos limpios! El código completo está aquí: https://github.com/PavelAndreev13/NetworkLayer