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
.
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!
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