Wie Sie mit JSON in Swift arbeiten.

Wenn ihre Anwendung mit einer Webanwendung kommuniziert, werden die vom Server zurückgegebenen Informationen oft als JSON formatiert. Sie können die JSONSerialization-Klasse des Foundation-Frameworks verwenden, um JSON in Swift-Datentypen wie Dictionary, Array, String, Number und Bool zu konvertieren. Da Sie sich jedoch nicht sicher sein können, ob die Struktur oder die Werte von JSON in ihrer Anwendung vorhanden sind, kann es schwierig sein, Modellobjekte korrekt zu deserialisieren. Dieser Beitrag beschreibt einige Ansätze, die Sie bei der Arbeit mit JSON in ihren Anwendungen verfolgen können.

JSON Swift

Werte aus JSON extrahieren.

Die JSONSerialization-Klassenmethode jsonObject (with: Options) gibt einen Wert vom Typ Any zurück und gibt einen Fehler aus, wenn die Daten nicht geparst werden konnten.

Copy to Clipboard

Obwohl gültiger JSON nur einen einzigen Wert enthalten kann, kodiert eine Antwort einer Web-Anwendung typischerweise ein Objekt oder Array als Top-Level-Objekt. Nutzen Sie alternativ das Binding und geben Sie den Operator cast in eine if- oder guard-Anweisung ein, um einen Wert des bekannten Typs als Konstante zu extrahieren. Um einen Dictionary-Wert von einem JSON-Objekttyp zu erhalten, casten Sie ihn bedingt als [String: Any]. Um einen Array-Wert von einem JSON-Array-Typ zu erhalten, müssen Sie ihn bedingt als [Any] (oder ein Array mit einem spezifischeren Elementtyp, wie z.B. [String]) ausgeben. Sie können einen Wörterbuchwert nach Schlüssel oder einen Arraywert nach Index extrahieren, indem Sie den Typ cast optionale Bindung mit Indexzugriffen oder Musterabgleich mit Aufzählung verwenden.

Copy to Clipboard

Die integrierten Sprachfunktionen von Swift erleichtern das sichere Extrahieren und Arbeiten mit JSON-Daten, die mit Foundation-APIs dekodiert wurden – ohne die Notwendigkeit einer externen Bibliothek oder eines Frameworks.

Modellobjekte aus JSON-extrahierten Werten anlegen.

Da die meisten Swift-Anwendungen dem Model-View-Controller-Design folgen, ist es oft sinnvoll, JSON-Daten in Objekte zu konvertieren, die für die Domain ihrer App in einer Modelldefinition spezifisch sind.

Wenn Sie beispielsweise eine Anwendung schreiben, die Suchergebnisse für lokale Restaurants liefert, können Sie ein Restaurant-Modell mit einem Initializer implementieren, der ein JSON-Objekt und eine Typmethode akzeptiert, die eine HTTP-Anfrage an den Endpunkt /search eines Servers stellt und dann asynchron ein Array von Restaurant-Objekten zurückgibt.

Betrachten Sie das folgende Restaurant-Modell:

Copy to Clipboard

Ein Restaurant hat einen Namen vom Typ String, einen Ort, der als Koordinatenpaar ausgedrückt wird und einen Satz von Mahlzeiten, die Werte einer verschachtelten Mahlzeitenaufzählung enthalten.

Hier ist ein Beispiel, wie ein einzelnes Restaurant in einer Serverantwort dargestellt werden kann:

Copy to Clipboard

Schreiben eines optionalen JSON-Initializers.

Um von einer JSON-Darstellung in ein Restaurant-Objekt zu konvertieren, schreiben Sie einen Initalizer, der ein Any-Argument verwendet, das Daten aus der JSON-Darstellung extrahiert und in Eigenschaften umwandelt.

Copy to Clipboard

Wenn ihre Anwendung mit einem oder mehreren Webservices kommuniziert, die nicht eine einzige, konsistente Darstellung eines Modellobjekts zurückgeben, sollten Sie in Erwägung ziehen, mehrere Initialisierungen zu implementieren, um jede der möglichen Darstellungen zu behandeln.

Im obigen Beispiel wird jeder der Werte in Konstanten aus dem übergebenen JSON-Wörterbuch mittels optionaler Bindung und dem Casting-Operator vom Typ? Extrahiert. Für die Eigenschaft name wird der extrahierte Namenswert einfach so zugewiesen, wie er ist. Für die Koordinateneigenschaft werden die extrahierten Breiten- und Längengrade vor der Zuweisung zu einem Tupel zusammengefasst. Für die Eigenschaft meals werden die extrahierten String-Werte iteriert, um einen Satz von Aufzählungswerten für Mahlzeiten zu erstellen.

Schreiben eines JSON-Initializers mit Fehlerbehandlung.

Das vorherige Beispiel implementiert einen optionalen Initializer, der null zurückgibt, wenn die Deserialisierung fehlschlägt. Alternativ können Sie einen dem Fehlerprotokoll entsprechenden Typ definieren und einen Initializer implementieren, der einen Fehler dieses Typs auslöst, wenn die Deserialisierung fehlschlägt.

Copy to Clipboard

Hier deklariert der Restauranttyp einen geschachtelten SerializationError-Typ, der Aufzählungsfälle mit zugehörigen Werten für fehlende oder ungültige Eigenschaften definiert. In der Wurfversion der JSON-Initialisatoren wird kein Fehler angezeigt, sondern ein Fehler geworfen, um den spezifischen Fehler zu melden. Diese Version führt auch eine Validierung der Eingabedaten durch, um sicherzustellen, dass die Koordinaten ein gültiges geographisches Koordinatenpaar darstellen und dass jeder der im JSON angegebenen Namen für Mahlzeiten den Fällen der Mahlzeitenaufzählung entspricht.

Schreiben einer Typmethode für das Holen von Ergebnissen.

Ein Web-Anwendungsendpunkt gibt oft mehrere Ressourcen in einer einzigen JSON-Antwort zurück. Ein /search-Endpunkt kann beispielsweise null oder mehr Restaurants zurückgeben, die dem angeforderten Abfrageparameter entsprechen und diese Darstellungen zusammen mit anderen Metadaten enthalten:

Copy to Clipboard

Sie können auf der Restaurant-Struktur eine Typmethode anlegen, die einen Query-Methodenparameter in ein entsprechendes Request-Objekt operationalisiert und den HTTP-Request an den Webservice sendet, Dieser Code wäre auch für die Behandlung der Antwort, die Deserialisierung der JSON-Daten, die Erstellung von Restaurant-Objekten aus jedem der extrahierten Wörterbücher im „results“-Array und deren asynchrone Rückgabe in einem Completion-Handler zuständig.

Copy to Clipboard

Ein View-Controller kann diese Methode aufrufen, wenn der Benutzer Text in eine Suchleiste eingibt, um eine Tabellenansicht mit passenden Restaurants zu füllen:

Copy to Clipboard

Eine solche Trennung bietet eine konsistente Schnittstelle für den Zugriff auf Restaurantressourcen von View-Controllern, auch wenn sich die Implementierungsdetails über den Webservice ändern.

Reflektion über Reflexion.

Die Konvertierung zwischen Darstellungen der gleichen Daten, um zwischen verschiedenen Systemen zu kommunizieren, ist eine mühsame, wenn auch notwendige Aufgabe, um Software zu schreiben.

Da die Struktur dieser Darstellungen sehr ähnlich sein kann, ist es verlockend, eine übergeordnete Abstraktion zu erstellen, um automatisch zwischen diesen verschiedenen Darstellungen abzubilden. Beispielsweise könnte ein Typ ein Mapping zwischen snake_case JSON-Schlüsseln und cameICase-Eigenschaftsnamen definieren, um ein Modell von JSON mit Hilfe der Swift-reflection-APIs wie Mirror automatisch zu initialisieren.

Wir haben jedoch festgestellt, dass diese Art von Abstraktionen tendenziell keine signifikanten Vorteile gegenüber der herkömmlichen Verwendung von Swift-Sprachfunktionen bieten und es stattdessen schwieriger machen, Probleme zu debuggen oder Kantenfälle zu behandeln. Im obigen Beispiel extrahiert und mappt der Initializer nicht nur Werte aus JSON, sondern initialisiert auch komplexere Datentypen und führt eine domainspezifische Eingabevalidierung durch. Ein reflexionsbasierter Ansatz müsste große Anstrengungen unternehmen, um all diese Aufgaben zu erfüllen. Denken Sie daran, wenn Sie die verfügbaren Strategien für ihre eigene Anwendung evaluieren. Die Kosten für kleine Mengen der Vervielfältigung können erheblich geringer sein, als die Auswahl der falschen Abstraktionen.