Codierbarer Spickzettel

Codable war eines der Eckpfeiler-Features von Swift 4.0 und brachte eine unglaublich reibungslose Konvertierung zwischen Swift-Datentypen und JSON mit sich. In Swift 4.1 wurde es dann dank neuer Funktionen noch besser, und ich erwarte, dass in Zukunft noch größere Dinge kommen werden.

In diesem Artikel möchte ich schnelle Code-Beispiele bereitstellen, die Ihnen helfen, häufige Fragen zu beantworten und häufige Probleme zu lösen, und zwar mit Codable.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Die Infrastruktur für In-App-Abonnements zu erstellen und zu pflegen ist schwierig. Zum Glück gibt es einen besseren Weg. Mit RevenueCat können Sie Abonnements für Ihre App in wenigen Stunden implementieren, nicht in Monaten, so dass Sie sich wieder der Entwicklung Ihrer App widmen können.

Testen Sie es kostenlos

Sponsern Sie Hacking with Swift und erreichen Sie die größte Swift-Community der Welt!

JSON kodieren und dekodieren

Beginnen wir mit den Grundlagen: Konvertieren von JSON in Swift-Strukturen.

Zunächst haben wir hier etwas JSON, mit dem wir arbeiten können:

let json = """"""let data = Data(json.utf8)

Die letzte Zeile konvertiert es in ein Data-Objekt, denn damit arbeiten Codable-Decoder.

Als Nächstes müssen wir eine Swift-Struktur definieren, die unsere fertigen Daten enthält:

struct User: Codable { var name: String var age: Int}

Jetzt können wir weitermachen und die Dekodierung durchführen:

let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}

Das gibt „Paul“ aus, den Namen des ersten Benutzers im JSON.

Konvertierung von case

Ein häufiges Problem mit JSON ist, dass es eine andere Formatierung für seine Schlüsselnamen verwendet, als wir in Swift verwenden wollen. Sie könnten zum Beispiel „first_name“ in Ihrem JSON erhalten und müssen das in eine firstName-Eigenschaft umwandeln.

Eine offensichtliche Lösung wäre, entweder die JSON- oder die Swift-Typen so zu ändern, dass sie dieselbe Namenskonvention verwenden, aber das werden wir hier nicht tun. Stattdessen gehe ich davon aus, dass Sie Code wie diesen haben:

let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}

Damit das funktioniert, müssen wir nur eine Eigenschaft in unserem JSON-Decoder ändern:

let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase

Dies weist Swift an, Namen in Schlangenform (names_written_like_this) auf Namen in Kamelform (namesWrittenLikeThis) zu mappen.

Unterschiedliche Schlüsselnamen abbilden

Wenn Sie JSON-Schlüssel haben, die sich komplett von Ihren Swift-Eigenschaften unterscheiden, können Sie sie mit einer CodingKeys-Enum abbilden.

Schauen Sie sich dieses JSON an:

let json = """"""

Diese Schlüsselnamen sind nicht gut, und wir würden diese Daten gerne in eine Struktur wie diese konvertieren:

struct User: Codable { var firstName: String var lastName: String var age: Int}

Um das zu erreichen, müssen wir eine CodingKeys Enum deklarieren: eine Zuordnung, die Codable verwenden kann, um JSON-Namen in Eigenschaften für unsere Struktur zu konvertieren. Dies ist eine reguläre Enum, die Strings für ihre Rohwerte verwendet, so dass wir sowohl unseren Eigenschaftsnamen (den Enum-Fall) als auch den JSON-Namen (den Enum-Wert) gleichzeitig angeben können. Außerdem muss es dem CodingKey-Protokoll entsprechen, damit es mit dem Codable-Protokoll funktioniert.

Fügen Sie also dieses Enum zur Struktur hinzu:

enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}

Das wird nun in der Lage sein, das JSON wie geplant zu dekodieren.

Hinweis: Das Enum heißt CodingKeys und das Protokoll heißt CodingKey.

Arbeiten mit ISO-8601-Datumsangaben

Es gibt viele Möglichkeiten, mit Datumsangaben im Internet zu arbeiten, aber ISO-8601 ist die gebräuchlichste. Es kodiert das vollständige Datum im Format JJJJ-MM-TT, dann den Buchstaben „T“, um den Beginn der Zeitinformation zu signalisieren, dann die Zeit im Format HH:MM:SS und schließlich eine Zeitzone. Die Zeitzone „Z“, kurz für „Zulu time“, wird üblicherweise für UTC verwendet.

Codable kann ISO-8601 mit einem eingebauten Datumsumrechner verarbeiten. So können wir dieses JSON wie folgt dekodieren:

let json = """"""

Das ermöglicht die Analyse des ISO-8601-Datums, das von 1999-04-03T17:30:31Z in eine Date-Instanz umgewandelt wird, wobei auch die Konvertierung von Schlangen- in Kamelschreibweise erfolgt.

Arbeiten mit anderen gebräuchlichen Datumsformaten

Swift bietet integrierte Unterstützung für drei weitere wichtige Datumsformate. Sie werden genauso verwendet wie ISO-8601-Datumsangaben, wie oben gezeigt, daher werde ich nur kurz auf sie eingehen:

  • Das Format .deferredToDate ist Apples eigenes Datumsformat, das die Anzahl der Sekunden und Millisekunden seit dem 1. Januar 2001 angibt. Außerhalb der Apple-Plattformen ist dies nicht wirklich nützlich.
  • Das Format .millisecondsSince1970 gibt die Anzahl der Sekunden und Millisekunden seit dem 1. Januar 1970 an. Dies ist online recht häufig.
  • Das Format .secondsSince1970 gibt die Anzahl der ganzen Sekunden seit dem 1. Januar 1970 an. Dieses Format ist online sehr verbreitet und wird nur von ISO-8601 übertroffen.

Arbeiten mit benutzerdefinierten Datumsangaben

Wenn Ihr Datumsformat nicht mit einer der integrierten Optionen übereinstimmt, brauchen Sie nicht zu verzweifeln: Codable kann benutzerdefinierte Datumsangaben auf der Grundlage eines von Ihnen erstellten Datumsformats parsen.

Dieses JSON erfasst beispielsweise den Tag, an dem ein Student sein Studium abgeschlossen hat:

let json = """"""

Dieses Format verwendet das Datumsformat DD-MM-YYYY, das nicht zu den in Swift integrierten Optionen gehört. Glücklicherweise können Sie eine vorkonfigurierte DateFormatter-Instanz als Datumsdecodierungsstrategie bereitstellen, wie hier:

let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)

Arbeiten mit seltsamen Datumsangaben

Manchmal erhalten Sie so seltsame Datumsangaben, dass selbst DateFormatter sie nicht verarbeiten kann. Zum Beispiel könnte man JSON erhalten, das Daten in der Anzahl der Tage speichert, die seit dem 1. Januar 1970 vergangen sind:

let json = """"""

Damit das funktioniert, müssen wir einen eigenen Decoder für das Datum schreiben. Alles andere wird immer noch von Codable gehandhabt – wir stellen nur eine benutzerdefinierte Schließung zur Verfügung, die den Datums-Teil verarbeitet:

Man könnte versuchen, hier ein wenig Mathematik zu betreiben, z.B. die Anzahl der Tage mit 86400 zu multiplizieren (die Anzahl der Sekunden in einem Tag) und dann die addTimeInterval()-Methode von Date zu verwenden. Das berücksichtigt jedoch nicht die Sommerzeit und andere Datumsprobleme, so dass eine bessere Lösung darin besteht, DateComponents und Calendar wie folgt zu verwenden:

let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .custom { decoder in // pull out the number of days from Codable let container = try decoder.singleValueContainer() let numberOfDays = try container.decode(Int.self) // create a start date of Jan 1st 1970, then a DateComponents instance for our JSON days let startDate = Date(timeIntervalSince1970: 0) var components = DateComponents() components.day = numberOfDays // create a Calendar and use it to measure the difference between the two let calendar = Calendar(identifier: .gregorian) return calendar.date(byAdding: components, to: startDate) ?? Date()}

Warnung: Wenn Sie viele Datumsangaben parsen müssen, denken Sie daran, dass diese Schließung für jede einzelne ausgeführt wird – machen Sie es schnell!

Parsen von hierarchischen Daten auf die einfache Art

Jedes nicht-triviale JSON hat wahrscheinlich hierarchische Daten – eine Sammlung von Daten, die in einer anderen verschachtelt sind. Zum Beispiel:

let json = """"""

Codable ist in der Lage, dies gut zu handhaben, solange man die Beziehungen klar beschreiben kann.

Ich finde, der einfachste Weg, dies zu tun, ist die Verwendung von verschachtelten Strukturen, wie diese:

struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}

Der Nachteil ist, dass, wenn Sie den Vornamen eines Benutzers lesen wollen, müssen Sie user.name.firstName verwenden, aber zumindest die eigentliche Parsing-Arbeit ist trivial – unser bestehender Code funktioniert bereits!

Hierarchische Daten auf die harte Tour parsen

Wenn man hierarchische Daten in eine flache Struktur parsen will – d.h., Sie wollen user.firstName statt user.name.firstName schreiben können – dann müssen Sie selbst etwas parsen. Das ist aber nicht allzu schwer, und Codable macht es schön typsicher.

Erstellen Sie zunächst die Struktur, die Sie am Ende haben wollen:

struct User: Codable { var firstName: String var lastName: String var age: Int}

Zweitens müssen wir Codierungsschlüssel definieren, die beschreiben, wo die Daten in der Hierarchie zu finden sind.

Schauen wir uns das JSON noch einmal an:

let json = """"""

Wie Sie sehen können, gibt es an der Wurzel einen Schlüssel namens „Name“ und einen weiteren namens „Alter“, also müssen wir diese als unsere Wurzel-Codierschlüssel hinzufügen. Fügen Sie dies in Ihre Struktur ein:

enum CodingKeys: String, CodingKey { case name, age}

Innerhalb von „name“ befinden sich zwei weitere Schlüssel, „first_name“ und „last_name“, für die wir nun einige Codierschlüssel erstellen werden. Fügen Sie Folgendes hinzu:

enum NameCodingKeys: String, CodingKey { case firstName, lastName}

Jetzt kommt der schwierige Teil: Wir müssen einen benutzerdefinierten Initialisierer und eine benutzerdefinierte Codierungsmethode für unseren Typ schreiben. Beginnen Sie damit, diese leere Methode zu Ihrer Struktur hinzuzufügen:

init(from decoder: Decoder) throws {}

Innerhalb dieser Methode müssen wir als erstes versuchen, einen Container herauszuziehen, den wir mit den Schlüsseln unseres CodingKeys-Enums lesen können, etwa so:

let container = try decoder.container(keyedBy: CodingKeys.self)

Wenn das erledigt ist, können wir versuchen, unsere age-Eigenschaft zu lesen. Dies geschieht auf typsichere Weise: Sie geben den Typ an, den Sie dekodieren wollen (Int.self für unser Alter), zusammen mit einem Schlüsselnamen aus dem CodingKeys enum:

age = try container.decode(Int.self, forKey: .age)

Nun müssen wir eine Ebene tiefer gehen, um unsere Namensdaten zu lesen. Wie Sie vorhin gesehen haben, ist „name“ ein Schlüssel der obersten Ebene innerhalb unserer CodingKeys Aufzählung, aber es ist eigentlich ein verschachtelter Container mit anderen Werten, die wir darin lesen müssen. Wir müssen also diesen Container herausziehen:

let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)

Und schließlich können wir zwei Strings für die Schlüssel .firstName und .lastName lesen:

firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)

Damit ist der benutzerdefinierte Initialisierer fertig, aber wir müssen noch eine weitere Methode schreiben: encode(to:). Dies ist praktisch die Umkehrung des Initialisierers, den wir gerade geschrieben haben, denn seine Aufgabe ist es, unsere Eigenschaften zurück in verschachtelte Container zu konvertieren.

Das bedeutet, dass wir einen Container auf der Basis unserer CodingKeys-Enum erstellen und dort age schreiben, dann einen verschachtelten Container auf der Basis unserer NameCodingKeys-Enum erstellen und dort sowohl firstName als auch lastName schreiben:

func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(age, forKey: .age) var name = container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name) try name.encode(firstName, forKey: .firstName) try name.encode(lastName, forKey: .lastName)}

Damit ist unser Code fertig. Jetzt können wir die firstName-Eigenschaft direkt auslesen, was viel schöner ist!

Hacking with Swift is sponsored by RevenueCat

SPONSORED Die Infrastruktur für In-App-Abonnements zu erstellen und zu pflegen ist schwierig. Zum Glück gibt es einen besseren Weg. Mit RevenueCat können Sie Abonnements für Ihre App in wenigen Stunden implementieren, nicht in Monaten, sodass Sie sich wieder der Entwicklung Ihrer App widmen können.

Testen Sie es kostenlos

Sponsern Sie Hacking with Swift und erreichen Sie die weltweit größte Swift-Community!

Leave a Reply