10. November 2018

JSON-Daten in Swift

JSON-Daten

Das JSON-Format (JavaScript Object Notation) ist ein sehr einfaches Datenformat, welches sich aufgrund seiner einfach lesbaren Textform für den strukturierten Datenaustausch zwischen Anwendungen sehr gut eignet. Dabei werden Daten über assoziative Arrays {...} und Listen [...] strukturiert, beispielsweise:

[
    {
        "name": "Bob",
        "age": 32,
        "phone": [
            { "type": "Handy", "number": "0123-456789" },
            { "type": "Festnetz", "number": "040-456789" }
        ]
    },
    {
        "name": "Alice",
        "age": "56",
        "address": {
            "street": "Musterstrasse 12",
            "zip": "20095",
            "city": "Hamburg"
        }
    }    
]

JSON-Daten in Swift verarbeiten

JSON.playground Die Foundation-Klassen JSONDecoder/JSONEncoder erlauben eine Konvertierung von JSON-Daten zu Swift-Typen. Dabei empfiehlt es sich, Datentypen als struct zu definieren, die 1:1 der Struktur aus dem JSON entsprechen. Für das obige Personen-Beispieldokument könnten folgende Typen definiert werden und mit dem Protokoll Codable versehen werden:

struct Phone : Codable {
    var type : String
    var number : String
}

struct Address : Codable {
    var street : String
    var zip : String
    var city : String
}

struct Person : Codable {
    var name : String
    var age : Int
    var phone : [Phone]?
}

Ein Data-Objekt mit JSON-Daten könnte folgendermaßen geparst werden:

let jsonDecoder = JSONDecoder()
let person = try! jsonDecoder.decode(Person.self, from: jsonData)

Ein Codable-Objekt kann folgendermaßen als JSON verpackt werden:

let jsonEncoder = JSONEncoder()
let jsonResultData = try! jsonEncoder.encode(person)

Tutorial 1: JSON-Daten über URLs laden

Ergebnis JSON-Anzeige Im Folgenden wird das Backend so erweitert, dass die JSON-Daten tatsächlich über URL-Zugriffe geladen werden.

Dabei erfolgt die Implementierung zunächst auf die einfachst mögliche Art und Weise und wird in Folgekapiteln verbessert.

  1. Laden Sie den Start-Stand von dem Flashcards-Beispielprojekt: Flashcards.zip.

  2. Rufen Sie http://www.ralfebert.de/flashcards/sets.json im Browser auf. Beachten Sie dass dieser Aufruf nicht direkt eine Liste mit zurückliefert, sondern die Daten nochmal in einem Dictionary mit einer Eigenschaft set verpackt sind.

  3. Deklarieren Sie daher einen weiteren Typ SetList für das äußere Dictionary. Deklarieren sie beide konform zu dem Protokoll Codable:

    struct SetList : Codable {
        var sets : [SetDownload]
    }
    
    struct SetDownload : Codable {
        var id : Int
        var title : String
        var term_count : Int
    }
  4. Passen Sie FlashcardsAPIClient#allSets so an, das der Typ SetList statt [SetDownload] verwendet wird. Implementieren Sie die Methode als Computed Property und laden Sie die Daten von der URL http://www.ralfebert.de/flashcards/sets.json.

    Verwenden Sie dazu die Methode Data(contentsOf: URL) - Hinweis: Diese Implementierung lädt die Daten synchron - die App muss auf die Fertigstellung des URL-Zugriffs warten. Dies ist lediglich ein erster Schritt und sollte in echten Apps so nicht erfolgen. Dies wird in einem Folgekapitel verbessert.

    Dekodieren Sie die JSON-Daten mit der JSONDecoder-Klasse in den entsprechenden Strukturtyp.

    Behandeln Sie dabei auftretende Fehler mit try!:

    class DemoFlashcardsAPIClient : FlashcardsAPIClient {
    
        var allSets: SetList {
            let url = URL(string: "https://www.ralfebert.de/flashcards/sets.json")!
            let data = try! Data(contentsOf: url)
            let json = try! JSONDecoder().decode(SetList.self, from: data)
            return json
        }
    
    }

    Hinweis: Für das Beispiel werden https-URLs verwendet, da seit iOS 9 der Zugriff über unverschlüsselte http-URLs eine explizite App-Transport-Security-Deklaration erfordert.

  5. Starten Sie die App und prüfen Sie, dass die Anzeige der JSON-Daten korrekt erfolgt. Committen Sie Ihre Änderungen: „15.1. Download von JSON-Daten“.

Tutorial 2: Download von Kartenstapeln

  1. Öffnen Sie im Browser http://www.ralfebert.de/flashcards/161.json und machen Sie sich mit dem JSON-Format für einen einzelnen Kartenstapel vertraut:

    {
        "id": 161,
        "title": "Spanish/English: Animals",
        "terms": [
            {
                "term": "ardilla",
                "definition": "squirrel"
            },
            {
                "term": "Ballena",
                "definition": "Whale"
            },
            {
                "term": "Bandada",
                "definition": "Flock of birds"
            }
        ]
    }
  2. Definieren Sie entsprechende Typen TermList und Term in FlashcardsAPIClient.swift.

    struct TermList : Codable {
        var id : Int
        var title : String
        var terms : [Term]
    }
    
    struct Term : Codable {
        var term : String
        var definition : String
    }
  3. Implementieren Sie die Methode in DemoFlashcardsAPIClient analog zu der bereits bestehenden Methode:

    func downloadSet(id : Int) -> TermList {
        let url = URL(string: "https://www.ralfebert.de/flashcards/\(id).json")!
        let data = try! Data(contentsOf: url)
        let json = try! JSONDecoder().decode(TermList.self, from: data)
        return json
    }
  4. Erweitern Sie die tableView(didSelectRowAt:)-Methode des DownloadsTableViewController so, dass der Kartenstapel tatsächlich heruntergeladen wird und die Anzahl der geladenen Terms angezeigt wird.

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let set = allSets[indexPath.row]
        let termList = backend.downloadSet(id: set.id)
        
        let alert = UIAlertController(title: "Download",
                     message: "Kartenstapel mit \(termList.terms.count) Karten heruntergeladen.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
  5. Starten Sie die App und testen Sie den Download eines Kartenstapels.

  6. Committen Sie Ihre Änderungen: „15.2. Download von Kartenstapeln“.