2. März 2020

JSON-Daten in Swift

JSON-Daten

Das JSON-Format (JavaScript Object Notation) eignet sich aufgrund seiner einfach lesbaren Textform sehr gut für den Austausch strukturierter Daten zwischen Anwendungen. Dabei werden Daten über assoziative Arrays {...} und Listen [...] repräsentiert, 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

Mit den Foundation-Klassen JSONDecoder und JSONEncoder können JSON-Daten in Swift geparst bzw. Swift-Werte als JSON serialisiert werden. Dabei empfiehlt es sich, Datentypen als struct zu definieren, die 1:1 der Struktur aus dem JSON entsprechen. Diese müssen mit dem Protokoll Codable versehen werden:

import Foundation

struct Person: Codable {
    let name: String
    let age: Int
    let phone: [Phone]?
    let address: Address?
}

struct Address: Codable {
    let street, zip, city: String
}

struct Phone: Codable {
    let type, number: String
}

Eine JSON-Datei, die im Xcode-Projekt enthalten ist und mit der App ausgeliefert wird, könnte folgendermaßen folgendermaßen geparst werden:

if let jsonURL = Bundle.main.url(forResource: "persons", withExtension: "json") {
    let jsonData = try Data(contentsOf: jsonURL)
    let jsonDecoder = JSONDecoder()
    let persons = try jsonDecoder.decode([Person].self, from: jsonData)
}

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

let jsonEncoder = JSONEncoder()
let jsonResultData = try jsonEncoder.encode(persons)

Namen der Eigenschaften anpassen, zusätzliche Eigenschaften

Mit einem CodingKeys-Enum können die Namen der Eigenschaften explizit festgelegt werden. Dazu ist es erforderlich, alle Eigenschaften als Coding-Key aufzunehmen. Eigenschaften die nicht aufgelistet sind, werden vom JSONDe/-Encoder ignoriert. So lassen sich auch zusätzliche Eigenschaften in einem JSON-Typ aufnehmen, die nicht mit serialisiert werden sollen:

struct Person: Codable {
    let name: String
    let alter: Int
    var someOtherAttribute: String?
    
    enum CodingKeys: String, CodingKey {
    case name
    case alter = "age"
}
}

Formatierte Ausgabe

Mit outputFormatting kann die Ausgabe der JSON-Serialisierung konfiguriert werden:

jsonEncoder.outputFormatting = .prettyPrinted

Benennung von Eigenschaften: Snake-case vs. Camel-case

Eigenschaften in JSON-Daten folgen oft der Snake-case-Namenskonvention (z.B. phone_number). Den Swift-Namenskonventionen folgend würde eine entsprechende Eigenschaft nach Camel-case-Namenskonvention (z.B. phoneNumber) benannt werden. Praktischerweise kann eine entsprechende Konvertierung der Namenskonventionen konfiguriert werden:

jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
jsonEncoder.keyEncodingStrategy = .convertToSnakeCase

Kodierung von Datumswerten

Für die nicht-standardisiert JSON-Kodierung von Datumswerten kann eine entsprechende Kodierungs-Strategie konfiguriert werden:

jsonEncoder.dateEncodingStrategy = .iso8601
jsonDecoder.dateDecodingStrategy = .iso8601

Dynamische Datenformate als Dictionary verwenden

Für JSON-Daten mit einem sehr dynamischen Format, das nicht mit Swift-Typen abgebildet werden kann, besteht die Möglichkeit die Daten via JSONSerialization ohne Mapping zu Swift-Typen als Dictionaries zu verwenden, zum Beispiel:

let dictionary = try JSONSerialization.jsonObject(with: someJsonData, options: []) as! [String:Any]
let name = dictionary["name"]

let serializedData = try JSONSerialization.data(withJSONObject: dictionary, options: [])