最近、多くの開発者と同様に、大きな複雑な JSON 対応に直面したとき、私の最初の本能は「迅速な修正」ツールに到達することでした。 さまざまな JSON-to-Swift コンバータ、そして現代のAI モデルでさえ、混乱した繰り返しのデータ構造に対処します。 Quicktype 正直に言うと、私は完全に落ち込んでいました。 タイトル: The “Flat” JSON Nightmare この問題は、クリーンな配列の代わりに「フラット」番号化プロパティを使用する古い API または構造の悪い応答に遭遇した場合に発生します。 { "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 } ] } なぜオンラインコンバーターが失敗するのか 私はこれを標準の変換ツールに接続したとき、その結果はメンテナンスの悪夢でした。 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? } 正直に言うと、これらのオンラインツールによって生成されたコードは、あらゆる深刻なプロジェクトの「ゴミ箱」に属します。 それはスケールできないだけでなく、彼らが40以上のオプションのプロパティを見るとPRレビュー中に上級開発者の顔を見ることを想像してください。 私はデコードプロセスをコントロールすることに決め、それをクリーンにし、スピードにし、そして最も重要なことは、 以下は、私がこのソリューションを構成した方法と、なぜ機能するのかです。 production-ready The Secret Weapon: Why We Use a Struct for CodingKeys(コードキーのための構造を使用する理由) Swiftのチュートリアルの99%で、あなたは 定義 A Enums は、コンパイル時に各キーを知っているときに素晴らしいですが、私たちのケースでは、キーのような「フラット」 JSON があります。 で、 up to 40のケースでエノムを書くことは退屈なことではなく、悪いエンジニアリングです。 代わりに CodingKeys enum strIngredient1 strIngredient2 20 struct 1.プロトコル要件の違反 に合致するため 男は両方扱わなければならない。 そして タイトル: By using a struct, we can pass Runtime で Initializer に入力します。 CodingKey String Int どんな 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 } 「Ugly」キーをクリーンネームにマッピング あなたのアプリ内でAPIの命名規約に固執する必要はありません。 これにより、解読論理の残りの部分を読み取ることができ、この構造の内部に「汚れた」APIキーが隔離されている。 static var static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") 3.Dynamic Key Generationの力 これは、このアプローチをAIによって生成されたコードよりも優れている部分です. We created static functions that use 飛行機でキーを生成する。 string interpolation static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } ハードコードの代わりに で、 たとえば、私たちは今、「鍵工場」を持っています。 私たちのイニシアライザーでは、これらの機能を単に呼びます. それはクリーンで、再利用可能で、40個の個別のケースを書くよりも、文字を書くのがはるかに難しい。 strIngredient1 strIngredient2 1...20 4. 実際に意味のあるモデルを構築する オリジナルJSONは、成分とその測定を異なる家に住む2人の見知らぬ人のように扱います。当社のアプリでは、カップルがあります。専用の構造を巣立てることで、私たちはデータアーキテクチャをソースで修正します。 struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } なぜ そして、The ? Hashable id ハッシュ id I added an なぜ? なぜ? なぜ? なぜ? なぜ? なぜ? なぜ? なぜ? なぜ? なぜ? なぜ? そして 認証データを必要としますので、 我々は保証する: id List ForEach Hashable ユーザーインターフェイスが混乱しない: SwiftUI は、2 つの異なるコンポーネントが同じ名前(「塩」の 2 つの異なるタイプのように)を持っている場合に混乱することはありません。 パフォーマンス: Diffable データ ソースは、ハッシュ 可能なオブジェクトを愛します。 5.「APIの匂い」を消す 初期化に至る前に、私たちがどのように主な属性を定義しているかを調べてみましょう. We are not just copying what the API gives us; we are translating it into. . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Goodbye str Prefix: We dropped the Hungarian notation. name is better than strMeal. 私たちはハンガリー語のノートを落としました。 適切なタイプ: マイナス画像をURLに直接解読しますか? API が破損したリンクまたは空の文字列を送信する場合は、解析段階のうちに、View では後でなく、解析段階で解析器が優雅に処理します。 6.The Smart Initializer: Our “Data Bouncer” (スマート・イニシアライザ) JSONが提供するすべてのキーを盲目的に受け入れる代わりに、私たちのカスタマイズ クラブでボーンサーのように振る舞う - 正当なデータだけが入る。 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 } 最終結果:Clean, Swifty, and UI-Ready 結局のところ、舞台の裏で働くこと、我々が達成したことを見てみましょう. 我々は「平らな」JSONの悪夢を、使える喜びのモデルに変えました. これがあなたのアプリの残りの部分が今見ていることです: struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } UIにおける純粋なシンプルさ 解読段階で重いリフトを行ったため - 空の値をフィルタリングし、成分をグループ化 - 私たちの SwiftUI コードは信じられないほど清潔になります。 トップページ > The Cherry on Top: Making Mocking Easy あなたは小さな副作用に気づいたかもしれません:私たちが習慣を定義するとき , Swift はデフォルトのメンバー向け初期化を停止します. This can make writing unit tests or SwiftUI Previews a bit annoying. init(from: Decoder) これを修正し、私たちのコードベースを「テストフレンドリー」に保つために、私たちはこの簡単な拡張機能を追加することができます. This allows us to create "Mock" data for our UI without needing a JSON file. 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 } } 今、プレビューを作成することは、以下のように簡単です。 let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) 結論 次回、あなたが混乱したAPIに直面するとき、思い出してください: オンラインツールとAIはあなたに迅速な「コピーペスト」ソリューションを与えるかもしれないが、それはしばしば技術的な負債につながる。 実装すると、以下のようなコードを作成します。 don’t let the backend dictate your frontend architecture. Decodable 読みやすい:明確で意図的な不動産名。 強力:ソースの空のデータまたは破損したデータをフィルターします。 保守性:テストしやすいし、UIで表示しやすい。 ハッピーコード、そしてあなたのモデルをきれいに保つ! 完全なコードはこちら: https://github.com/PavelAndreev13/NetworkLayer