Πρόσφατα αντιμετώπισα μια ενδιαφέρουσα πρόκληση που αφορούσε την αποκωδικοποίηση JSON στο Swift. Όπως πολλοί προγραμματιστές, όταν αντιμετώπισα μια μεγάλη, πολύπλοκη απάντηση 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 } ] } Γιατί αποτυγχάνουν οι online μετατροπές Όταν συνδέθηκα αυτό σε τυπικά εργαλεία μετατροπής, το αποτέλεσμα ήταν ένας εφιάλτης συντήρησης. 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+ προαιρετικές ιδιότητες. Αποφάσισα να αναλάβω τον έλεγχο της διαδικασίας αποκωδικοποίησης για να την κάνω καθαρή, γρήγορη και - το πιο σημαντικό - Εδώ είναι πώς δομήσαμε τη λύση και γιατί λειτουργεί. production-ready Το μυστικό όπλο: Γιατί χρησιμοποιούμε μια δομή για κλειδιά κωδικοποίησης Στο 99% των Swift tutorials, μπορείτε να δείτε Ορίζεται ως ένα Τα enums είναι υπέροχα όταν γνωρίζετε κάθε κλειδί κατά τη στιγμή της σύνταξης.Αλλά στην περίπτωσή μας, έχουμε ένα "πλάσιο" JSON με κλειδιά όπως , ... μέχρι Γράφοντας ένα έγγραφο με 40 περιπτώσεις δεν είναι απλά βαρετό - είναι κακή μηχανική. Αντ’ αυτού CodingKeys enum strIngredient1 strIngredient2 20 struct Παραβίαση των απαιτήσεων του πρωτοκόλλου Για να συμμορφωθούν Ένας άνθρωπος πρέπει να χειριστεί και τα δύο και Χρησιμοποιώντας μια δομή, μπορούμε να περάσουμε Πληκτρολογήστε το αρχικοποιητή στο runtime. 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") Η δύναμη της δυναμικής γενιάς κλειδιών Αυτό είναι το μέρος που κάνει αυτή την προσέγγιση ανώτερη από οποιονδήποτε κώδικα που παράγεται από την τεχνητή νοημοσύνη. για να δημιουργήσετε κλειδιά στο αεροπλάνο. string interpolation static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } Αντί για hardcoding , κλπ, έχουμε τώρα ένα «κλειδί εργοστάσιο». Είναι καθαρό, είναι επαναχρησιμοποιήσιμο, και είναι σημαντικά πιο δύσκολο να κάνετε μια γραφή από το να γράψετε 40 μεμονωμένες περιπτώσεις. strIngredient1 strIngredient2 1...20 Δημιουργία ενός μοντέλου που έχει πραγματικά νόημα Το πρωτότυπο JSON αντιμετωπίζει ένα συστατικό και τις μετρήσεις του ως δύο ξένους που ζουν σε διαφορετικά σπίτια. Στην εφαρμογή μας, υπάρχει ένα ζευγάρι. struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } ΓΙΑΤΙ Και η ? Hashable id Χασάμπα ΙΔ Προσθέσαμε ένα Επόμενο άρθροΓιατί; Επειδή οι σύγχρονες προβολές του SwiftUI και απαιτούνται δεδομένα ταυτοποίησης. με τη συμμόρφωση με Εμείς διασφαλίζουμε : id List ForEach Hashable Χωρίς παρεμβολές του UI: Το SwiftUI δεν θα μπερδευτεί αν δύο διαφορετικά συστατικά έχουν το ίδιο όνομα (όπως δύο διαφορετικοί τύποι «Αλάτι»). Απόδοση: Οι πηγές δεδομένων με διαφάνεια αγαπούν τα αντικείμενα με διαφάνεια. Καθαρίστε την «βρώμικη μυρωδιά» Πριν φτάσουμε στον αρχικοποιητή, δείτε πώς ορίζουμε τις κύριες ιδιότητές μας.Δεν αντιγράφουμε μόνο αυτό που μας δίνει το API, το μεταφράζουμε σε . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Αντίο str Πρόθεμα: Έχουμε ρίξει την ουγγρική σηματοδότηση. το όνομα είναι καλύτερο από το strMeal. Εάν το API στέλνει μια σπασμένη σύνδεση ή μια κενή ουρά, ο αποκωδικοποιητής μας το χειρίζεται ευχάριστα κατά τη φάση ανάλυσης, όχι αργότερα στην προβολή. Ο έξυπνος αρχικοποιητής: ο «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 και UI-Ready Μετά από όλα αυτά τα έργα πίσω από τις σκηνές, δείτε τι έχουμε επιτύχει. Έχουμε μετατρέψει έναν «επίπεδη» εφιάλτη JSON σε ένα μοντέλο που είναι μια χαρά να χρησιμοποιήσετε. struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Καθαρή απλότητα στο UI Επειδή κάναμε τη βαριά ανύψωση κατά τη φάση αποκωδικοποίησης - φιλτράρισμα κενών τιμών και ομαδοποίηση συστατικών - ο κώδικας SwiftUI μας γίνεται απίστευτα καθαρός. Το κεράσι στην κορυφή: Κάντε το γέλιο εύκολο Μπορεί να έχετε παρατηρήσει μια μικρή παρενέργεια: όταν ορίζουμε μια συνήθεια , Η Swift σταματά να παράγει τον προεπιλεγμένο αρχικοποιητή μέλους. Αυτό μπορεί να κάνει τις δοκιμές μονάδας γραφής ή τις προβολές SwiftUI λίγο ενοχλητικές. 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 } } Τώρα, η δημιουργία μιας προεπισκόπησης είναι τόσο απλή όσο: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) Συμπέρασμα Την επόμενη φορά που θα βρεθείτε αντιμέτωποι με ένα μπερδεμένο API, θυμηθείτε: Τα διαδικτυακά εργαλεία και η τεχνητή νοημοσύνη μπορεί να σας δώσουν μια γρήγορη λύση "copy-paste", αλλά συχνά οδηγούν σε τεχνικό χρέος. Κατά την εφαρμογή, δημιουργείτε κώδικα που είναι: don’t let the backend dictate your frontend architecture. Decodable Διαβαστό: σαφή, βάσει προθέσεων ονόματα ακινήτων. Robust: Φιλτράρει τα κενά ή κατεστραμμένα δεδομένα στην πηγή. Διατηρήσιμη: Εύκολη δοκιμή και εύκολη εμφάνιση στο UI. Καλή κωδικοποίηση και κρατήστε τα μοντέλα σας καθαρά! Ο πλήρης κώδικας είναι εδώ: https://github.com/PavelAndreev13/NetworkLayer