JSON Decoding in Swift. როგორც ბევრი განვითარებლები, როდესაც შეესაბამება დიდი, კომპლექსური JSON პასუხი, ჩემი პირველი ინტენსია იყო "ჩქარე გადარჩენა" ინსტრუმენტები. მე მინდა იხილოთ, თუ როგორ ჩვენი პოპულარული ონლაინ რესურსები - როგორიცაა სხვადასხვა JSON-to-Swift კონვერტორები, და მაშინაც კი თანამედროვე AI მოდელები - შეეხება რთული, repetitive მონაცემთა სტრუქტურა. Quicktype სიტყვად, მე იყო სრულიად შეუზღუდავი. The Problem: The “Flat” JSON Nightmare ეს პრობლემა იწვევს, როდესაც თქვენ შეხვდება ძველი API- ს ან არასწორი სტრუქტურული პასუხი, რომელიც იყენებს "ხალახული" ნომერირებული თვისებები სუფთა შეზღუდვისა. იხილეთ ეს JSON ნიმუში: { "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 } ] } რატომ Online Converters გაქცევა როდესაც მე ამ სტანდარტული კონვერტაციის ინსტრუმენტებს შეუწყე, შედეგად შენარჩუნების ცუდი იყო. ისინი აწარმოებენ "მართობი თვისებები", რომელიც იმიტომ, რომ ეს იყო: 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? } გაითვალისწინეთ, რომ კოდი, რომელიც ამ ონლაინ ინსტრუმენტების მიერ შექმნილია, ნებისმიერ სერიოზული პროექტისთვის "პროფონზე" არის. არა მხოლოდ ეს არ არის შეზღუდული, არამედ ვფიქრობთ, რომ თქვენი წამყვანი განვითარებლის სახის თვალსაზრისით PR მიმოხილვა დროს, როდესაც ისინი იხილებენ 40+ უპირატესობები. ეს არის შენარჩუნების საწოლი და თქვენი პროფესიული რეპუტაცია. მე გადაწყვეტა მიიღოს კონტროლი decoding პროცესი, რათა იგი სუფთა, Swifty, და - ყველაზე მნიშვნელოვანია - აქ არის, თუ როგორ მე სტრუქტურა გადაწყვეტილება და რატომ მუშაობს. production-ready Secret Weapon: რატომ ვიყენებთ Struct for CodingKeys 99% Swift tutorials, თქვენ ნახავთ განმარტებული როგორც Enums არის დიდი, როდესაც თქვენ იცით, თითოეული კურსი კომპიუტერის დროში. მაგრამ ჩვენი შემთხვევაში, ჩვენ გვაქვს "flat" JSON კურსი, როგორიცაა და Up to შეტყობინება 40 შემთხვევაში არ არის მხოლოდ ნედლეული - ეს არის ცუდი ინჟინერი. ეს არის მიზეზი, რომ ჩვენ გამოიყენებთ instead CodingKeys enum strIngredient1 strIngredient2 20 struct 1. მოთხოვნების შეზღუდვა კონტაქტი შეესაბამება ერთი ადამიანი უნდა გააკეთოს ორივე და ფუნქციონირება: ჩვენ შეგვიძლია გამოიყენოთ ფუნქციონირება დასაწყისში დასაწყისში დასაწყისში დასაწყისში. 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 } 2. რუკირება “Ugly” Key to Clean Names თქვენ არ უნდა შეესაბამება API- ის ნომერი კონტენციები თქვენი app- ში. შეამოწმეთ, თუ როგორ მე გამოიყენება ეს ხელს უწყობს, რომ rest of the decoding logic ითვლება, ხოლო "შავი" API keys იზოლებული ამ სტრუქტში. static var static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") Dynamic Key გენერაციის ძალა ეს არის ნაწილი, რომელიც ამ მიმოხილვა უმაღლესი ნებისმიერი AI- ის გენერირებული კოდი. ჩვენ შექმნილია სტატისტიკური ფუნქციები, რომლებიც გამოიყენოს აწარმოებს ღილაკებს თვითმფრინავებში. string interpolation static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } Hardcoding საწყისი და და ა.შ., ჩვენ ახლა გვაქვს "კოვანი ქარხანა". როდესაც ჩვენ loop through ჩვენ უბრალოდ ამ ფუნქციებს ვთქვათ, რომ ეს სუფთაა, ეს ვერ გამოიყენება, და ეს მნიშვნელოვანია უფრო რთულია, ვიდრე 40 პირდაპირი შემთხვევაში. strIngredient1 strIngredient2 1...20 4. აშენება მოდელი, რომელიც ნამდვილად გულისხმობს JSON- ს ორიგინალური ინგრედიენტს და მისი შეზღუდვა შეესაბამება, როგორიცაა ორი ცუდი, რომლებიც განსხვავებული სახლში ცხოვრობენ. ჩვენი აპლიკაციაში, არსებობს რამდენიმე. მასშტაბით, რაც სპეციალიზებული სტრუქტურა, ჩვენ გადარჩენა მონაცემთა არქიტექტურა წყაროში: struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } რატომ და The ? Hashable ID ჰაერის ID მე დაამატა კეთილდღეობა, რომელიც გამოიყენება loop index. Why? Because modern SwiftUI views like და მოითხოვს დაინახულებადი მონაცემები. შეესაბამება ჩვენ უზრუნველყოფს : id List ForEach Hashable No UI Glitches: SwiftUI არ იქნება შეშფოთებული, თუ ორი განსხვავებული ინგრედიენტები აქვს იგივე სახელი (გალითად, ორი განსხვავებული ტიპის "Salt"). შესრულება: Diffable მონაცემთა წყაროები მიყვარს hashable Objects. 5. გაწმენდა “API Smell” ადრე, ვიცით, თუ როგორ ვფიქრობთ ჩვენი ძირითადი თვისებები. ჩვენ არ უბრალოდ კუპირებთ ის, რაც API გთავაზობთ; ჩვენ გადაიხადეთ ეს. . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Goodbye str Prefix: We dropped the Hungarian notation. სახელი უკეთესია, ვიდრე strMeal. განკუთვნილია, თუ API- ის გადაცემა შეუზღუდავი კავშირი ან ფურცელი string, ჩვენი decoder აწარმოებს ეს გარიგებით ფანჯარა ფაზის დროს, არა შემდეგ View- ში. Smart Initializer: ჩვენი “Data Bouncer” ეს არის საბოლოო. ვიდრე ყურადღებით მიიღოს თითოეული კურსი, რომელიც JSON გთავაზობთ, ჩვენი საბოლოო ფუნქციონირებს, როგორიცაა bouncer Club - მხოლოდ valid მონაცემები მოდის. 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 და UI-ready რა თქმა უნდა, ეს მუშაობს სინათლის შემდეგ, იხილეთ, რაც ჩვენ გააკეთა. ჩვენ შეიცვალა "ფაილი" JSON საწოლი მოდელი, რომელიც არის ბედნიერი გამოყენება. ეს არის, რაც სხვა თქვენი app იხილავს ახლა: struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Pure Simplicity- ის გამოყენება იმიტომ, რომ ჩვენ გააკეთა მძიმე გაუმჯობესება decoding ფაზის დროს - ფიტნეს ფართო ღირებულებები და ჯგუფიგურაცია ინგრედიენტები - ჩვენი SwiftUI კოდი იღებს ძალიან სუფთა. ჩვენ არ გჭირდებათ ნებისმიერი კომპლექსური ლოგიკა View; ჩვენ უბრალოდ რუკა მონაცემები პირდაპირი კომპონენტები. The Cherry on Top: Making Mocking მარტივი თქვენ შეიძლება შეამოწმოთ ერთი მცირე გვერდითი ეფექტი: როდესაც ჩვენ განთავსებთ მოდული Swift- ს შეჩერებს სტანდარტული წევრობის ინტილიზატორს. ეს შეიძლება გააკეთოთ წერილის ერთეული ტესტიები ან SwiftUI Previews- ის პატარა უარყოფითი. init(from: Decoder) იმისათვის, რომ ეს გააკეთა და ჩვენი კოდიბაჟის “ტესტირების მეგობრული” შენარჩუნება, ჩვენ შეგვიძლია ამ მარტივი გაფართოებას დაამატოთ. ეს საშუალებას გაძლევთ შექმნათ “Mock” მონაცემები ჩვენი UI- ს გარეშე 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 } } ახლა, შექმნა preview არის როგორც მარტივი, როგორც: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) კონტაქტი შემდეგი დროს, როდესაც თქვენ შეხვდება რთული API, გაითვალისწინეთ: ონლაინ ინსტრუმენტები და AI შეიძლება უზრუნველყოს სწრაფი "copy-paste" გადაწყვეტილება, მაგრამ ისინი ხშირად იწვევს ტექნიკური ფული. განახლება, თქვენ შექმნათ კოდი, რომელიც არის: don’t let the backend dictate your frontend architecture. Decodable სინამდვილეში: სინამდვილეში დასაწყისში დასაწყისში. Robust: Filters out ფოლადის ან გაფართოებული მონაცემები წყაროში. მარტივი ტესტირება და მარტივი ჩვენება UI- ში. Happy coding, და შენარჩუნეთ თქვენი მოდელები სუფთა! სრული კოდი აქ: https://github.com/PavelAndreev13/NetworkLayer