Lernziel
Nach dieser Lektion kannst du JSON lesen und verstehen, Swift-Typen mit Codable aus JSON erzeugen und wieder in JSON umwandeln, abweichende Namen mit CodingKeys zuordnen, fehlende Werte mit Optionals abfangen und Datumswerte richtig dekodieren.
Das Problem: Daten in einer fremden Sprache
Fast jede App bekommt Daten von außen: Wetterdaten, Rezepte, Nachrichten, Kontostände. Diese Daten kommen nicht als Swift-Structs an, sondern als Text in einem standardisierten Format. Das mit Abstand häufigste Format heißt JSON.
Bevor deine App mit den Daten arbeiten kann, müssen sie übersetzt werden: aus JSON-Text in deine eigenen Typen. Und manchmal auch umgekehrt, wenn du Daten an einen Server schickst oder speicherst.
In Swift erledigt das ein Protokoll namens Codable, und meist mit erstaunlich wenig Code.
Was ist JSON?
JSON steht für JavaScript Object Notation. Es ist ein Textformat, das Menschen lesen und Computer leicht verarbeiten können. So sieht ein Rezept als JSON aus:
{
"id": 52772,
"name": "Teriyaki Chicken",
"category": "Chicken",
"minutes": 35,
"isVegetarian": false,
"rating": 4.6,
"tags": ["Asiatisch", "Schnell"],
"author": {
"name": "Anna",
"country": "Österreich"
}
}JSON kennt nur wenige Bausteine, und du kennst alle aus Stufe 1:
| JSON | Beispiel | Swift |
|---|---|---|
Objekt { … } | {"name": "Anna"} | Struct oder Dictionary |
Array [ … ] | ["Asiatisch", "Schnell"] | Array |
| Text | "Teriyaki Chicken" | String |
| Ganze Zahl | 35 | Int |
| Kommazahl | 4.6 | Double |
| Wahrheitswert | true, false | Bool |
| Nichts | null | nil (Optional) |
Ein JSON-Objekt ist eine Sammlung von Schlüssel-Wert-Paaren, genau wie ein Dictionary (Lektion 1.7). Die Schlüssel stehen immer in Anführungszeichen.
Von JSON zu Swift: Decodable
Um dieses JSON in Swift zu verwenden, baust du eine passende Struct und lässt sie Codable erfüllen:
struct Author: Codable {
let name: String
let country: String
}
struct Recipe: Codable, Identifiable {
let id: Int
let name: String
let category: String
let minutes: Int
let isVegetarian: Bool
let rating: Double
let tags: [String]
let author: Author
}Die Regel ist einfach: Jeder Schlüssel im JSON wird zu einer Eigenschaft mit genau demselben Namen und passendem Typ. Verschachtelte Objekte werden zu eigenen Structs, wie hier Author.
Dann übersetzt ein JSONDecoder den Text:
import Foundation
let json = """
{
"id": 52772,
"name": "Teriyaki Chicken",
"category": "Chicken",
"minutes": 35,
"isVegetarian": false,
"rating": 4.6,
"tags": ["Asiatisch", "Schnell"],
"author": { "name": "Anna", "country": "Österreich" }
}
"""
let data = Data(json.utf8)
do {
let recipe = try JSONDecoder().decode(Recipe.self, from: data)
print(recipe.name) // Teriyaki Chicken
print(recipe.author.name) // Anna
print(recipe.tags.count) // 2
} catch {
print("Fehler beim Lesen: \(error)")
}Data(json.utf8)wandelt den Text in Bytes um. Daten aus dem Internet kommen ohnehin alsDataan.JSONDecoder().decode(Recipe.self, from: data)heißt: „Mach aus diesen Bytes einRecipe.“Recipe.selfist der Typ selbst (Lektion 3.3).- Die Funktion ist
throws(Lektion 1.17): Passt das JSON nicht zur Struct, gibt es einen Fehler.
Tipp
Swift erzeugt den gesamten Übersetzungscode automatisch, weil alle Eigenschaften selbst Codable sind: String, Int, Double, Bool, Arrays davon und andere Codable-Structs. Genau wie bei Equatable (Lektion 1.15) schreibst du nur den Namen des Protokolls.
Arrays von Objekten
APIs liefern oft eine Liste:
[
{ "id": 1, "name": "Pizza" },
{ "id": 2, "name": "Pasta" }
]Dann dekodierst du ein Array:
struct Dish: Codable {
let id: Int
let name: String
}
let dishes = try JSONDecoder().decode([Dish].self, from: data)Verpackte Antworten
Sehr häufig steckt die eigentliche Liste in einem äußeren Objekt:
{
"meals": [
{ "id": 1, "name": "Pizza" },
{ "id": 2, "name": "Pasta" }
]
}Dann brauchst du eine kleine Struct für die Hülle:
struct MealResponse: Codable {
let meals: [Dish]
}
let response = try JSONDecoder().decode(MealResponse.self, from: data)
let dishes = response.mealsDiese Art von Antwort wirst du in Projekt D sehen.
Nur, was du brauchst
Du musst nicht jedes Feld aus dem JSON übernehmen. Eigenschaften, die in deiner Struct fehlen, werden einfach ignoriert:
struct RecipeSummary: Codable {
let id: Int
let name: String
}Diese Struct liest aus dem langen Rezept-JSON nur id und name. Alles andere wird übersprungen. Das ist praktisch, denn viele APIs liefern deutlich mehr, als eine App braucht.
Fehlende Werte: Optionals
Umgekehrt ist es kritisch: Erwartet deine Struct ein Feld, das im JSON fehlt oder null ist, schlägt das Dekodieren fehl. Bei Feldern, die nicht immer vorhanden sind, verwendest du deshalb ein Optional (Lektion 1.8):
struct Recipe: Codable {
let id: Int
let name: String
let imageURL: String? // nicht jedes Rezept hat ein Bild
let videoURL: String?
}Fehlt imageURL im JSON oder steht dort null, wird die Eigenschaft einfach nil.
Achtung
Die häufigste Fehlerquelle bei JSON: ein Feld als nicht-optional deklariert, das in manchen Datensätzen fehlt. Mit Testdaten funktioniert alles, bei echten Daten stürzt das Laden ab. Lies die Dokumentation der API genau und mach im Zweifel Felder optional.
Andere Namen: CodingKeys
JSON-Schlüssel folgen nicht immer den Swift-Konventionen. Oft heißen sie image_url mit Unterstrich, strMeal mit seltsamen Vorsilben oder einfach anders, als du es willst. Mit CodingKeys ordnest du sie zu:
{ "idMeal": "52772", "strMeal": "Teriyaki Chicken", "strMealThumb": "https://…" }struct Meal: Codable, Identifiable {
let id: String
let name: String
let thumbnail: String?
enum CodingKeys: String, CodingKey {
case id = "idMeal"
case name = "strMeal"
case thumbnail = "strMealThumb"
}
}CodingKeys ist ein Enum (Lektion 1.14) mit String-Raw-Values: Der Fall ist der Name in Swift, der Raw Value der Name im JSON. Sobald du CodingKeys angibst, musst du alle Eigenschaften aufführen, die dekodiert werden sollen.
Unterstriche automatisch umwandeln
Verwendet die API durchgehend Unterstriche (first_name, created_at), gibt es eine Abkürzung:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCaseDann wird first_name automatisch zu firstName, ganz ohne CodingKeys. Diese Schreibweise mit Unterstrichen heißt snake_case.
Datumswerte
JSON hat keinen eigenen Datumstyp. Daten kommen meist als Text im Format ISO 8601 an:
{ "title": "Treffen", "date": "2026-10-20T16:00:00Z" }Damit JSONDecoder daraus ein Date macht, sagst du ihm, welches Format er erwarten soll:
struct Event: Codable {
let title: String
let date: Date
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let event = try decoder.decode(Event.self, from: data)Von Swift zu JSON: Encodable
Codable funktioniert in beide Richtungen. Mit JSONEncoder wird aus deiner Struct wieder JSON:
let dish = Dish(id: 3, name: "Kaiserschmarrn")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(dish)
print(String(decoding: data, as: UTF8.self))Ausgabe:
{
"id" : 3,
"name" : "Kaiserschmarrn"
}.prettyPrinted sorgt für Einrückungen. Ohne steht alles in einer Zeile, was kürzer, aber schwerer lesbar ist.
Das brauchst du, wenn du Daten an einen Server schickst oder, wie in Lektion 3.13, Daten auf dem Gerät speicherst.
Tipp
Codable ist eigentlich eine Abkürzung für zwei Protokolle: Decodable (aus JSON lesen) und Encodable (in JSON schreiben). Brauchst du nur eine Richtung, reicht das jeweilige Protokoll.
Fehler beim Dekodieren verstehen
Passt das JSON nicht zur Struct, ist die Fehlermeldung zunächst unübersichtlich. Mit einem genaueren catch wird sie hilfreich:
do {
let recipe = try JSONDecoder().decode(Recipe.self, from: data)
} catch DecodingError.keyNotFound(let key, _) {
print("Fehlendes Feld: \(key.stringValue)")
} catch DecodingError.typeMismatch(let type, let context) {
print("Falscher Typ \(type) bei \(context.codingPath.map(\.stringValue))")
} catch {
print("Anderer Fehler: \(error)")
}| Fehler | Bedeutung | Typische Lösung |
|---|---|---|
keyNotFound | Ein Feld fehlt im JSON | Eigenschaft optional machen oder CodingKeys prüfen |
typeMismatch | Falscher Typ, z. B. "35" statt 35 | Typ der Eigenschaft anpassen |
valueNotFound | Feld ist null, aber nicht optional | Eigenschaft optional machen |
dataCorrupted | Kein gültiges JSON oder falsches Datumsformat | JSON und dateDecodingStrategy prüfen |
Typische Fehler
Zahl als Text
Manche APIs liefern Zahlen in Anführungszeichen: "id": "52772". Dann muss die Eigenschaft ein String sein, kein Int. Sonst gibt es typeMismatch.
Tippfehler im Namen
let catgory: String findet im JSON kein catgory und wirft keyNotFound. Namen müssen exakt übereinstimmen, inklusive Groß- und Kleinschreibung.
CodingKeys unvollständig
Sobald es CodingKeys gibt, werden nur die dort aufgeführten Eigenschaften dekodiert. Fehlt eine Eigenschaft im Enum, die keinen Standardwert hat:
Fehlermeldung
Type 'Meal' does not conform to protocol 'Decodable'
let id = UUID() und Codable
struct Item: Codable {
let id = UUID()
let name: String
}Achtung
Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten
Die id wird beim Dekodieren immer neu erzeugt und nicht aus dem JSON gelesen. Kommt die id aus dem JSON, deklariere sie ohne Startwert: let id: Int.
FAQ
Anfänger
Was bedeutet das überhaupt?Wie finde ich heraus, wie das JSON einer API aussieht? Öffne die Adresse der API im Browser. Viele Browser zeigen JSON übersichtlich formatiert an. Außerdem steht es meist in der Dokumentation der API.
Muss ich jede Struct von Hand schreiben? Ja, aber es ist schnell gemacht: Schlüssel für Schlüssel abschreiben und den passenden Typ wählen. Es gibt auch Werkzeuge, die aus JSON-Beispielen Structs erzeugen. Prüf deren Ergebnis aber immer, vor allem bei Optionals.
Ist JSON das einzige Format?
Nein, aber das mit Abstand häufigste. Mit PropertyListEncoder gibt es zum Beispiel auch Apples eigenes Format, das intern für Einstellungen verwendet wird.
Fortgeschritten
Warum verhält es sich so?Wie lese ich ein Feld, das mal Zahl, mal Text ist?
Mit einem eigenen init(from decoder: Decoder). Darin versuchst du zuerst try? container.decode(Int.self, forKey: .id) und, falls das scheitert, die Text-Variante. Solche Sonderfälle sind lästig, aber manchmal nötig.
Wie flache ich verschachteltes JSON in eine einfache Struct ab?
Ebenfalls mit einem eigenen init(from:) und nestedContainer(keyedBy:forKey:). Oft ist es aber einfacher, die verschachtelte Struktur 1:1 nachzubauen und eine berechnete Eigenschaft für den bequemen Zugriff zu ergänzen.
Profi
Was passiert unter der Haube?Wie funktioniert die automatische Erzeugung?
Der Compiler erzeugt für Codable-Typen, deren Eigenschaften alle selbst Codable sind, ein verstecktes CodingKeys-Enum, einen init(from: Decoder) und eine Methode encode(to: Encoder). Sie lesen bzw. schreiben jede Eigenschaft über einen Keyed Container. Schreibst du eines davon selbst, verwendet Swift deine Version und erzeugt nur die fehlenden Teile.
Ist Codable an JSON gebunden?
Nein. Codable beschreibt nur, welche Werte ein Typ hat. Das Format bestimmt der Encoder bzw. Decoder: JSON, Property Lists oder eigene Formate. Dieselbe Struct kann mit verschiedenen Encodern in verschiedene Formate geschrieben werden.
Übungen
Übung 1: Wetter dekodieren
Dekodiere im Playground dieses JSON in eine passende Struct und gib „Wien: 18.5 °C, sonnig“ aus:
{ "city": "Wien", "temperature": 18.5, "condition": "sonnig" }Lösung Übung 1Erst selbst probieren!
import Foundation
struct Weather: Codable {
let city: String
let temperature: Double
let condition: String
}
let json = """
{ "city": "Wien", "temperature": 18.5, "condition": "sonnig" }
"""
do {
let weather = try JSONDecoder().decode(Weather.self, from: Data(json.utf8))
print("\(weather.city): \(weather.temperature) °C, \(weather.condition)")
} catch {
print(error)
}Übung 2: Seltsame Namen
Dekodiere dieses JSON in eine Struct mit den Eigenschaften id, title, price und isOnSale:
{ "products": [
{ "product_id": 1, "product_title": "Kaffee", "price_eur": 4.5, "on_sale": true },
{ "product_id": 2, "product_title": "Tee", "price_eur": 3.2, "on_sale": false }
] }Lösung Übung 2Erst selbst probieren!
struct Product: Codable {
let id: Int
let title: String
let price: Double
let isOnSale: Bool
enum CodingKeys: String, CodingKey {
case id = "product_id"
case title = "product_title"
case price = "price_eur"
case isOnSale = "on_sale"
}
}
struct ProductResponse: Codable {
let products: [Product]
}
let response = try JSONDecoder().decode(ProductResponse.self, from: Data(json.utf8))
for product in response.products {
print(product.title, product.price, product.isOnSale)
}Übung 3: Hin und zurück
Leg ein Array mit drei Gewohnheiten an (eine Codable-Version der Habit-Struct aus Projekt C mit id: UUID, Name und completions: [Date]). Kodiere es mit .iso8601 und .prettyPrinted zu JSON, gib es aus, dekodiere es wieder und prüf mit ==, ob das Ergebnis dem Original entspricht. Dafür muss Habit auch Equatable sein.
Zusammenfassung
- JSON ist das Standardformat für Daten aus dem Internet: Objekte, Arrays, Texte, Zahlen, Bools und
null. Codableübersetzt automatisch zwischen JSON und Swift-Typen, wenn die Namen übereinstimmen.JSONDecoder().decode(Typ.self, from: data)liest,JSONEncoder().encode(wert)schreibt.- Felder, die fehlen können, werden Optionals.
CodingKeysordnet abweichende Namen zu,.convertFromSnakeCasewandelt Unterstriche automatisch um..iso8601dekodiert Datumswerte im Standardformat.DecodingErrorverrät, welches Feld nicht passt.
Spickzettel
struct Meal: Codable, Identifiable {
let id: String
let name: String
let thumbnail: String?
enum CodingKeys: String, CodingKey {
case id = "idMeal", name = "strMeal", thumbnail = "strMealThumb"
}
}
struct MealResponse: Codable { let meals: [Meal]? }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
let response = try decoder.decode(MealResponse.self, from: data)
let data = try JSONEncoder().encode(meal)