NEW: Get your ticket for Hacking with Swift Live 2019! >>

< Previous: Die grundlegende UI erstellen: UITabBarController   Next: Eine Petition rendern: loadHTMLString >

JSON parsen mit dem Codable-Protokoll

JSON - kurz für JavaScript Object Notation - ist eine Art, Daten zu beschreiben. Es ist nicht gerade das lesbarste Format, aber es ist kompakt und kann leicht von Computern geparst werden, was es online beliebt macht, wo Bandbreite kostbar ist.

Bevor wir uns ans Parsen machen, hier ein winziger Ausschnitt der tatsächlichen JSON, welche du empfangen wirst:

{
    "metadata":{
        "responseInfo":{
            "status":200,
            "developerMessage":"OK",
        }
    },
    "results":[
        {
            "title":"Legal immigrants should get freedom before undocumented immigrants – moral, just and fair",
            "body":"I am petitioning President Trump's Administration to take a humane view of the plight of legal immigrants. Specifically, legal immigrants in Employment Based (EB) category. I believe, such immigrants were short changed in the recently announced reforms via Executive Action (EA), which was otherwise long due and a welcome announcement.",
            "issues":[
                {
                    "id":"28",
                    "name":"Human Rights"
                },
                {
                    "id":"29",
                    "name":"Immigration"
                }
            ],
            "signatureThreshold":100000,
            "signatureCount":267,
            "signaturesNeeded":99733,
        },
        {
            "title":"National database for police shootings.",
            "body":"There is no reliable national data on how many people are shot by police officers each year. In signing this petition, I am urging the President to bring an end to this absence of visibility by creating a federally controlled, publicly accessible database of officer-involved shootings.",
            "issues":[
                {
                    "id":"28",
                    "name":"Human Rights"
                }
            ],
            "signatureThreshold":100000,
            "signatureCount":17453,
            "signaturesNeeded":82547,
        }
    ]
}

Tatsächlich wirst du 2000-3000 Zeilen von diesem Zeug kriegen, die Petitionen von US-Bürgern über alle möglichen politischen Dinge enthalten. Es ist eigentlich gar nicht wichtig (für uns), um was es bei den Petitionen geht, wir interessieren uns nur für die Datenstruktur. Insbesondere:

  1. Es gibt einen Metadaten-Wert, der einen Wert namens responseInfo enthält, der wiederum einen Status-Wert enthält. Mit Status 200 meinen Internet-Entwickler, dass "alles OK" ist.
  2. Es gibt einen Resultat-Wert, der eine Reihe von Petitionen enthält.
  3. Jede Petition enthält einen Titel, einen Textkörper, einige Sachverhalte, zu denen sie Bezug hat, und Informationen zu den Unterschriften.
  4. JSON hat auch Strings und Integers (Ganzzahlen). Beachte, wie alle Strings von Anführungszeichen umgeben sind, die Ganzzahlen aber nicht.

Swift hat eine eingebaute Unterstützung für JSON mit einem Protokoll namens Codable. Wenn du sagst, "meine Daten genügen Codable", wird Swift dir erlauben, mit nur wenig Code frei zwischen diesen Daten und JSON zu konvertieren.

Swifts einfache Typen wie String und Int genügen automatisch Codable, und Arrays und Dictionaries genügen ebenfalls Codable, wens sie Codable-Objekte enthalten. Das heißt, [String] genügt Codable problemlos, da String selbst Codable genügt.

Hier benötigen wir allerdings etwas Komplexeres: jede Petition enthält einen Titel, einen Textkörper, die Anzahl der Unterschriften, und mehr. Das heißt, wir müssen unseren eigenen maßgeschneiderten Datentyp definieren anstatt einfach nur String oder Int zu verwenden.

Swift hat zwei Wege, um eigene Datentypen zu erzeugen, die als Structs und Klassen bekannt sind. Wir haben Klassen schon verwendet - alle unsere UIViewController-Subklassen sind Klassen - aber wenn du mit eigenen Daten arbeitest anstatt eine Subklasse zu erzeugen, ist es im Allgemeinen besser, mit Structs zu arbeiten, da sie einfacher sind.

Wir werden ein eigenes Struct Petition erzeugen, das eine Petition aus unserer JSON enthält, das heißt, es wird den Titel-String, den Textkörper-String, und die Anzahl der Unterschriften, eine Ganzzahl, speichern. Drücke daher Cmd+N und wähle eine neue Swift-Datei mit dem Namen Petition.swift.

struct Petition {
    var title: String
    var body: String
    var signatureCount: Int
}

Dies definiert ein spezielles Struct mit drei Eigenschaften. Einer der Vorteile von Structs in Swift ist, dass sie uns einen elementweisen Initialisierer geben - eine spezielle Funktion, die neue Petition-Instanzen erzeugen kann, indem Werte für title, body und signatureCount übergeben werden.

Dazu kommen wir gleich, aber zunächst hatte ich das Codable-Protokoll erwähnt. Unser Petition Struct enthält zwei Strings und eine Ganzzahl, die alle bereits Codable genügen, daher können wir Swift sagen, dass der ganze Petition-Typ Codable erfüllen soll, und zwar so:

struct Petition: Codable {
    var title: String
    var body: String
    var signatureCount: Int
}

Mit dieser simplen Änderung sind wir beinahe soweit, Instanzen von Petition aus JSON zu laden.

Ich sage beinahe soweit, denn da ist noch ein kleiner Haken in unserem Plan: wenn du dir das oben gegebene JSON-Beispiel angeschaut hast, ist dir aufgefallen, dass unserer Petitions-Array tatsächlich innerhalb eines Dictionaries namens "results" liegt. Das bedeutet, wenn wir versuchen, Swift den JSON-Code parsen zu lassen, müssen wir zuerst den Schlüssel laden, und dann darin das Array der Petitions-Resulate laden.

Swifts Codable-Protokoll muss exakt wissen, wo es die Daten findet, was in diesem Falle bedeutet, ein zweites Struct zu machen. Dieses wird eine einzelne Eigenschaft namens results haben, welches ein Array von unseren Petition-Structs ist. Das passt dann exakt dazu, wie JSON aussieht: die Haupt-JSON enthält das results-Array, und in jedem Element in diesem Array ist eine Petition.

Drücke also erneut Cmd+N, um eine neue Datei zu erzeugen, wähle Swift-Datei und nenne sie Petitions.swift. Gib ihr diesen Inhalt:

struct Petitions: Codable {
    var results: [Petition]
}

Ich sehe ein, dass das nach einer Menge Arbeit aussieht, aber vertrau mir: es wird noch viel leichter!

Alles, was wir getan haben, war, die Datenstrukturen zu definieren, in die wir die JSON laden wollen. Der nächste Schritt ist, eine Eigenschaft in ViewController zu erzeugen, die unser Petitions-Array speichert.

Wie du dich erinnerst, deklarierst du Arrays, indem du einfach den Datentyp in eckigen Klammern schreibst, so wie hier:

var petitions = [String]()

Wir wollen ein Array aus unserem Petition-Objekt machen. Das sieht dann so aus:

var petitions = [Petition]()

Ersetze damit die aktuelle petitions-Definition am Anfang von ViewController.swift.

Jetzt ist es an der Zeit, JSON zu parsen, was bedeutet, sie zu verarbeiten und die Inhalte zu prüfen. Wir fangen damit an, die viewDidLoad()-Methode für ViewController so anzupassen, dass sie die Daten vom Petitions-Server des Weißen Hauses herunterlädt, sie in ein Swift Data-Objekt umwandelt, und dann versucht, dieses in ein Array von Petition-Instanzen zu konvertieren.

Wir haben Data bisher noch nicht verwendet. Wie String und Int ist dies einer der fundamentalen Datentypen von Swift, auch wenn er auf noch niedrigerer Stufe angesiedelt ist - er enthält buchstäblich irgendwelche Binärdaten. Das könnte ein String sein, das könnte der Inhalt einer Zip-Datei sein, or buchstäblich irgendetwas anderes.

Data und String haben einige Dinge gemeinsam. Du hast bereits gesehen, dass ein String mit Hilfe von contentsOfFile erzeugt werden kann, um Daten von der Festplatte zu laden, und Data hat genau den gleichen Initialisierer. Beide können also mit einem contentsOf-Initialisierer erzeugt werden, der Daten von einer URL (mit URL spezifiziert) herunterlädt und dir zur Verfügung stellt.

Das ist für unsere Zwecke perfekt - hier ist die neue viewDidLoad-Methode:

override func viewDidLoad() {
    super.viewDidLoad()

    let urlString = "https://api.whitehouse.gov/v1/petitions.json?limit=100"

    if let url = URL(string: urlString) {
        if let data = try? Data(contentsOf: url) {
            // we're OK to parse!
        }
    }
}

Konzentrieren wir uns auf das Neue:

  • urlString verweist auf den Whitehouse.gov Server, um auf das Petitions-System zuzugreifen.
  • Wir nutzen if let, um sicherzustellen, dass die URL gültig ist, anstatt sie zwangsweise zu entpacken ("force unwrap").
  • Wir erzeugen ein neues Data-Objekt mit Hilfe der contentsOf-Methode. Diese liefert den Inhalt einer URL zurück, kann aber auch einen Fehler werfen (zum Beispiel, wenn keine Internetverbindung besteht), daher müssen wir try? verwenden.
  • Wenn das Data-Objekt erfolgreich erzeugt wurde, erreichen wir die Zeile "we're OK to parse" ("wir sind bereit zum Parsen"). Diese beginnt mit //, womit eine Kommentarzeile in Swift bezeichnet wird. Kommentarzeilen werden vom Compiler ignoriert; wir schreiben sie als Notizen für uns selbst.

Dieser Code ist nicht perfekt, ganz im Gegenteil. Indem wir Daten aus dem Internet in viewDidLoad() laden, wird faktisch unsere App blockiert, bis alle Daten transferiert wurden. Es gibt dazu Lösungen, aber um es einfach zu halten, behandeln wir diese erst in Projekt 9.

Im Moment wollen wir uns auf das Parsen von JSON konzentrieren. We haben bereits ein petitions-Array, das bereit ist, ein Array von Petitionen aufzunehmen. Wir wollen Swifts Codable-System nutzen, um unsere JSON in das Array zu parsen, und sobald das fertig ist, sagen wir unserer Table View, dass sie sich selbst neu laden soll.

Bist du soweit? Denn dieser Code ist erstaunlich einfach für die viele Arbeit, die er erledigt:

func parse(json: Data) {
    let decoder = JSONDecoder()

    if let jsonPetitions = try? decoder.decode(Petitions.self, from: json) {
        petitions = jsonPetitions.results
        tableView.reloadData()
    }
}

Platziere die Methode direkt unterhalb von der viewDidLoad()-Methode, dann ersetze die Zeile // we're OK to parse! in viewDidLoad() mit dieser:

parse(json: data)

Die neue Methode parse() macht einige neue und interessante Dinge:

  1. Sie erzeugt eine Instanz von JSONDecoder, die speziell dafür ausgelegt ist, zwischen JSON und Codable-Objekten zu konvertieren.
  2. Sie ruft dann die Methode decode() dieses Dekoders auf, um unsere json-Daten in ein Petitions-Objekt zu konvertieren. Dieser Aufruf kann einen Fehler werfen, daher verwenden wir try?, um zu prüfen, ob es funktioniert hat.
  3. Wenn die JSON erfolgreich konvertiert wurde, weisen wir das results-Array unserer Eigenschaft petitions zu und laden die Table View neu.

Der Teil, den du bisher noch nicht gesehen hast, ist Petitions.self, was Swifts Art ist, den Petitions-Typ direkt zu bezeichnen anstatt einer Instanz davon. Das heißt, wir sagen nicht "erzeuge eine neue", sondern spezifizieren ihn stattdessen als Parameter für das Dekodieren, sodass JSONDecoder weiß, in was er die JSON konvertieren soll.

Du kannst das Programm jetzt laufen lassen, auch wenn es nur "Title goes here" und "Subtitle goes here" immer und immer wieder anzeigt, denn unsere cellForRowAt-Methode fügt nur Platzhalter-Daten hinzu.

Wir wollen das so modifizieren, dass die Zellen den title-Wert unseres Petition-Objekts ausgeben, aber wir wollen auch das Untertitel-Textlabel verwenden, das dazu kam, als wir den Zellentyp im Storyboard von "Basic" zu "Subtitle" geändert haben. Ändere dafür die Methode cellForRowAt wie folgt:

let petition = petitions[indexPath.row]
cell.textLabel?.text = petition.title
cell.detailTextLabel?.text = petition.body

Wir setzen die Schlüssel title, body und sigs im Objekt, daher können wir sie nun auslesen, um unsere Zelle korrekt zu konfigurieren.

Wenn du jetzt die App laufen lässt, siehst du, wie die Dinge sich ganz nett zusammen fügen - jede Tabellen-Zeile zeigt jetzt den Titel der Petition, und darunter stehen die ersten Worte des Textkörpers der Petition. Der Untertitel zeigt automatisch "..." am Ende an, wenn es nicht genug Platz für den gesamten Text gibt, aber das ist genug, um dem Nutzer einen Eindruck davon zu verschaffen, um was es geht.

HACKING WITH SWIFT LIVE This July is a new two-day event where you'll be inspired by great speakers on day one then learn all the amazing new features from WWDC on day two – click here for more information and tickets.

< Previous: Die grundlegende UI erstellen: UITabBarController   Next: Eine Petition rendern: loadHTMLString >
MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let me know!

Click here to visit the Hacking with Swift store >>