Geschreven door Reinder de Vries op 20 januari 2021 in App Development, iOS, Swift
Je kunt Codable
in Swift gebruiken om aangepaste dataformaten, zoals JSON, te coderen en decoderen naar native Swift-objecten. Het is ongelooflijk eenvoudig om Swift-objecten te koppelen aan JSON-gegevens, en omgekeerd, door simpelweg het Codable
-protocol te gebruiken.
Als pragmatische iOS-ontwikkelaar zul je JSON eerder vroeg dan laat tegenkomen. Elke webservice, van Facebook tot Foursquare, gebruikt het JSON-formaat om gegevens in en uit je app te krijgen. Hoe kun je die JSON data effectief coderen en decoderen naar Swift objecten?
In deze tutorial leer je hoe je kunt werken met JSON objecten in Swift, door gebruik te maken van het Codable
protocol. Wat je leert, kan gemakkelijk worden toegepast op andere data formaten ook. We gaan in op coderen en decoderen met JSONEncoder
en JSONDecoder
, en ik zal je laten zien hoe je kunt bemiddelen tussen JSON en Swift data.
Klaar? Laten we gaan.
- Get Start: Coderen en decoderen
- Het Codable-protocol uitgelegd
- Werken met Codable
- JSON decoderen naar Swift-objecten met Codable
- Swift-objecten coderen als JSON met Codable
- Werken met geneste arrays en Codable
- Verder lezen
Get Started: Encoding and Decoding
Welk probleem lost het Codable
protocol in Swift eigenlijk op? Laten we beginnen met een voorbeeld.
Stel je voor dat je een recepten-app aan het bouwen bent. De app toont verschillende recepten in een lijst, met inbegrip van de ingrediënten, instructies en basisinformatie over voedsel.
Je krijgt de gegevens voor de app van een webservice, en hun cloud-gebaseerde API. Deze API maakt gebruik van het JSON-gegevensformaat. Elke keer dat u een recept aanvraagt bij de webservice, krijgt u JSON-gegevens terug.
Hier volgt een voorbeeld van JSON-gegevens voor een recept:
{ "name": "Spaghetti Bolognese", "author": "Reinder's Cooking Corner", "url": "https://cookingcorner.com/spaghetti-bolognese", "yield": 4, "ingredients": , "instructions": "Cook spaghetti, fry beef and garlic, add tomatoes, add love, eat!"}
Kijk eens naar de structuur van de JSON-gegevens.
JSON-objecten zijn gewikkeld in schuine haakjes {
en }
, en arrays zijn gewikkeld in vierkante haakjes . Objectnamen zijn strings, tussen aanhalingstekens
"
. Waarden in JSON kunnen strings, getallen (geen aanhalingstekens), arrays of andere objecten zijn. Je kunt ook gegevens nesten, d.w.z. arrays in arrays, objecten in arrays, enz., om een complexe hiërarchie van gegevens te creëren.
JSON is een op tekst gebaseerd gegevensformaat dat veel webservices gebruiken, waaronder API’s van Twitter, Facebook, Foursquare, enzovoort. Als u apps bouwt die webgebaseerde bronnen gebruiken, zult u JSON tegenkomen.
Het JSON-formaat is superieur aan XML, een veelgebruikt alternatief, omdat het efficiënt, gemakkelijk te parseren en leesbaar is door mensen. JSON is een overeengekomen formaat voor webservices, API’s en apps. Het wordt overal op het web, in apps en online diensten gebruikt, omdat het formaat eenvoudig en flexibel is.
En JSON heeft één geweldige eigenschap: je kunt elk dataformaat in JSON coderen, en JSON terug decoderen naar elk dataformaat. Dit coderings- en decodeerproces is wat JSON zo krachtig maakt.
U kunt uw Swift String
, Int
, Double
, URL
, Date
, Data
, Array
en Dictionary
waarden nemen, en ze coderen als JSON. Vervolgens stuurt u ze naar de webservice, die de waarden decodeert in een native formaat dat hij begrijpt. Op dezelfde manier stuurt de webservice gegevens gecodeerd als JSON naar jouw app, en jij decodeert de gegevens naar native types zoals String
, Double
en Array
.
Wanneer jouw recept-app de JSON ontvangt (zie hierboven), kan deze vervolgens worden gedecodeerd naar een Swift struct, zoals deze:
struct Recipe { var name: String var author: String var url: URL var yield: Int var ingredients: var instructions: String}
In Swift wordt het Codable
protocol gebruikt om van een JSON data-object naar een daadwerkelijke Swift klasse of struct te gaan. Dit wordt decoderen genoemd, omdat de JSON gegevens worden gedecodeerd in een formaat dat Swift begrijpt. Het werkt ook de andere kant op: het coderen van Swift objecten als JSON.
Het Codable
protocol in Swift is eigenlijk een alias van de Decodable
en Encodable
protocollen. Omdat je vaak coderen en decoderen samen gebruikt, gebruik je het Codable
-protocol om beide protocollen in één keer te krijgen.
Het middelpunt van de encoding/decoding-workflow is het Codable
-protocol van Swift. Laten we eens kijken hoe het werkt!
Kan je “coderen” en “decoderen” niet uit elkaar houden? Zie het als volgt: We zetten data om van en naar “code”, zoals een Enigma machine of een geheime crypto code. Encoderen betekent het omzetten van gegevens naar code; en-coding, of “in/met code”. Decoderen betekent het omzetten van code naar gegevens; de-coding, of “van/uit code”.
Wordt aangenomen als iOS-ontwikkelaar
Leer iOS 14-apps bouwen met Swift 5
Schrijf je in voor mijn cursus iOS-ontwikkeling en leer hoe je je carrière als professionele iOS-ontwikkelaar kunt beginnen.
The Codable Protocol Explained
Het gebruik van JSON vóór Swift 4 was een beetje een PITA. Je moest de JSON zelf serializeren met JSONSerialization
, en dan elke eigenschap van de JSON naar het juiste Swift type type type casten.
let json = try? JSONSerialization.jsonObject(with: data, options: )if let recipe = json as? { if let yield = recipe as? Int { recipeObject.yield = yield }}
De bovenstaande snippet pakt alleen een yield
waarde uit de JSON, en casts het naar Int
. Het is extreem uitgebreid, en het is moeilijk om goed te reageren op mogelijke fouten en type verschillen. Hoewel het werkt, is het niet ideaal.
Bibliotheken zoals SwiftyJSON maken het werken met JSON een stuk eenvoudiger, maar je moet nog steeds de JSON data mappen naar hun respectievelijke Swift objecten en eigenschappen.
Met Swift 4, en later, kun je in plaats daarvan het Codable
protocol gebruiken. Je Swift struct of klasse hoeft alleen maar het Codable
protocol aan te nemen, en je krijgt JSON codering en decodering uit de doos, gratis.
Het Codable
protocol is een samenstelling van twee protocollen, Decodable en Encodable. Beide protocollen zijn vrij minimaal; ze definiëren alleen de functies init(from: Decoder)
en encode(to: Encoder)
, respectievelijk. Met andere woorden, deze functies betekenen dat voor een type om “decodeerbaar” of “codeerbaar” te zijn, zij moeten “decoderen van iets” en “coderen naar iets”.
De echte magie van Codable
gebeurt met de Decoder
en Encoder
protocollen. Deze protocollen worden overgenomen door de componenten die verschillende gegevensformaten coderen/decoderen, zoals JSON. In Swift hebben we een paar -coders:
-
PropertyListEncoder
enPropertyListDecoder
voor .plist property lijsten -
JSONEncoder
enJSONDecoder
voor JSON – dat zijn wij! - NSKeyedArchiver kan werken met
Codable
, viaPropertyListEncoder
,Data
enNSCoding
De JSONDecoder
en JSONEncoder
klassen gebruiken die de Decoder
en Encoder
protocollen om de functionaliteit te leveren om JSON te decoderen/encoderen. Dat betekent ook dat u uw eigen aangepaste encoder/decoder voor Codable
kunt schrijven, mits u de Decoder
– en Encoder
-protocollen gebruikt!
Wilt u een opfrisser over protocollen in Swift? Lees Protocols In Swift Explained om bij te komen.
Werken met Codable
Laten we eens kijken naar een voorbeeld. We gaan wat JSON-gegevens omzetten in een Swift-structuur. In wezen decoderen we de JSON naar een echt Swift object.
Eerst maken we een struct genaamd User
. Zoals dit:
struct User: Codable { var first_name: String var last_name: String var country: String}
De User
struct heeft drie eenvoudige eigenschappen van het type String
, en voldoet aan het Codable
protocol.
Dan, laten we een beetje JSON schrijven. Dit is de JSON waar we mee gaan werken:
{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}
JSON data komt typisch je app binnen als response van een webservice request, of via een .json
bestand, maar voor dit voorbeeld stoppen we de JSON gewoon in een String
. Zoals dit:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Note: De bovenstaande code gebruikt het drievoudige aanhalingsteken """
om strings van meerdere regels te maken. Netjes!
Wat we nu gaan doen, is de JSON decoderen en omzetten in een User
-object. Zoals dit:
let jsonData = jsonString.data(using: .utf8)!let user = try! JSONDecoder().decode(User.self, from: jsonData)print(user.last_name)// Output: Doe
Dit is wat er gebeurt:
- Eerst verander je
jsonString
in eenData
object door dedata(using:)
functie op de string aan te roepen. Dit is een noodzakelijke tussenstap. - Daarna maakt u een
JSONDecoder
-object en roept onmiddellijk dedecode(_:from:)
-functie daarop aan. Dit verandert dejsonData
in een object van het typeUser
, door het decoderen van de JSON. Het typeUser.self
wordt als parameter meegegeven. - Tot slot drukt u de achternaam van de gebruiker af met
print(user.last_name)
. Dieuser
waarde heeftUser
als zijn type, dus het is een echt Swift object!
Gemakkelijk, toch? Je hebt in wezen het JSON-object “gemapt” naar een Swift struct, en het JSON-formaat gedecodeerd naar een native object waar Swift mee kan werken.
In de bovenstaande code negeren we alle fouten die door decode(_:from:)
met try!
worden gegooid. In uw eigen code moet u fouten afhandelen met een do-try-catch
-blok. Een fout die kan worden gegooid, bijvoorbeeld, komt van het verstrekken van ongeldige JSON.
De User.self
is een metatype, een verwijzing naar het type User
zelf. We vertellen de decoder dat we de gegevens willen decoderen met de User
struct, door het een verwijzing naar dat type te geven.
Een veel voorkomende fout bij het definiëren van uw Codable
type, zoals de User
struct, is verkeerd gematchte sleutels en eigenschappen. Als je een eigenschap hebt toegevoegd aan je struct, die niet aanwezig is in de JSON of een ander type heeft, krijg je een fout bij het decoderen van de JSON. Het omgekeerde, d.w.z. een eigenschap die niet bestaat in je struct maar wel in de JSON, is geen probleem. Het is het gemakkelijkst om dit één eigenschap per keer te debuggen, d.w.z. blijf eigenschappen toevoegen totdat de JSON niet meer foutloos decodeert. U kunt ook bepalen welke eigenschappen/sleutels moeten worden opgenomen of genegeerd met het CodingKeys
-enum (zie hieronder).
JSON decoderen naar Swift-objecten met Codable
Laten we het voorbeeld uit de vorige sectie nog eens bekijken, en het uitbreiden. Hier is de JSON waar we mee werken:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Daar maken we dan een Data
object van. Zoals dit:
let jsonData = jsonString.data(using: .utf8)!
Dit is een noodzakelijke tussenstap. In plaats van de JSON als een string weer te geven, slaan we de JSON nu op als een native Data
object. De tekencodering die we voor die string gebruiken is UTF8.
Als je goed kijkt, zie je dat de bovenstaande code force unwrapping gebruikt om te werken met de optionele retourwaarde van data(using:)
. Laten we die optie op een elegantere manier uitpakken!
if let jsonData = jsonString.data(using: .utf8) { // Use `jsonData`} else { // Respond to error }
Met de bovenstaande code kunnen we reageren op fouten wanneer data(using:)
nil
teruggeeft. Je kunt bijvoorbeeld een foutmelding laten zien, of de taak stilletjes laten mislukken en diagnostische informatie in een log opslaan.
Volgende, we maken een nieuw JSONDecoder
object. Zoals dit:
let decoder = JSONDecoder()
We gebruiken dan die decoder om de JSON data te decoderen. Zoals dit:
let user = try! decoder.decode(User.self, from: jsonData)
De decode(_:from:)
functie kan echter fouten opleveren. De bovenstaande code crasht wanneer dat gebeurt. Nogmaals, we willen reageren op eventuele fouten die kunnen optreden. Zoals dit:
do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name)} catch { print(error.localizedDescription)}
En het hele code blok ziet er nu uit als het volgende. Zie je hoe dat verschilt?
if let jsonData = jsonString.data(using: .utf8){ let decoder = JSONDecoder() do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name) } catch { print(error.localizedDescription) }}
Fouten nooit verzwijgen. Vang de fout op en reageer erop, ofwel met UI/UX, door de taak opnieuw te proberen, of door te loggen, te crashen en te repareren.
Werken met CodingKeys
Wat als onze JSON-eigenschappen, zoals first_name
en/of firstName
, anders zijn dan de Swift struct-eigenschappen? Dat is waar CodingKeys
in het spel komt.
Elke klasse of struct die voldoet aan Codable
kan een speciale geneste opsomming declareren, CodingKeys
genaamd. U gebruikt deze om eigenschappen te definiëren die moeten worden gecodeerd en gedecodeerd, inclusief hun namen.
Laten we eens kijken naar een voorbeeld. In de User
struct hieronder, hebben we de namen van de eigenschappen veranderd van snake_case in camelCase. Bijvoorbeeld, de sleutel first_name
heet nu firstName
.
struct User:Codable { var firstName: String var lastName: String var country: String enum CodingKeys: String, CodingKey { case firstName = "first_name" case lastName = "last_name" case country }}
Wanneer u de bovenstaande struct gebruikt met de voorbeelden in de vorige secties, zult u zien dat u een User
object kunt gebruiken met de nieuwe eigenschap namen. Zoals dit:
print(user.firstName)// Output: Johnprint(user.country)// Output: United Kingdom
De CodingKeys
opsomming mapt zijn gevallen aan eigenschappen, en gebruikt de string waarden om de juiste eigenschap namen in de JSON data te selecteren. De case in de enum is de naam van de eigenschap die je wilt gebruiken in de struct, en de waarde is de naam van de sleutel in de JSON data.
Coderen van Swift objecten als JSON met Codable
Tot nu toe hebben we ons gericht op het decoderen van JSON data naar Swift objecten. Hoe zit het met de andere kant op? Kunnen we ook objecten coderen als JSON? Ja, waarom niet!
var user = User()user.firstName = "Bob"user.lastName = "and Alice"user.country = "Cryptoland"let jsonData = try! JSONEncoder().encode(user)let jsonString = String(data: jsonData, encoding: .utf8)!print(jsonString)
En de output is:
{"country":"Cryptoland","first_name":"Bob","last_name":"and Alice"}
Wat gebeurt hier?
- Eerst maakt u een
User
object en wijst u enkele waarden toe aan zijn eigenschappen - Dan gebruikt u
encode(_:)
om hetuser
object te coderen naar een JSONData
object - Finitief, converteert u het
Data
object naarString
en drukt het af
Als u goed kijkt, ziet u dat het coderen dezelfde stappen volgt als het decoderen, maar dan in omgekeerde volgorde. Het gaat van Swift object, door de decoder, resulterend in een JSON string.
Zoals voorheen, kunnen we het voorbeeld uitbreiden om met fouten om te gaan? Ja! Zoals dit:
let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinteddo { let jsonData = try encoder.encode(user) if let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) }} catch { print(error.localizedDescription)}
In het bovenstaande voorbeeld, gebruiken we ook de outputFormatting
eigenschap van de encoder om de JSON data “mooi af te drukken”. Dit voegt spaties, tabs en nieuwe regels toe om de JSON string leesbaarder te maken. Zoals dit:
{ "country" : "Cryptoland", "first_name" : "Bob", "last_name" : "and Alice"}
Awesome!
Het is de moeite waard om op te merken dat woordenboeken in Swift, en in JSON, geen vaste sorteervolgorde hebben. Daarom zul je verschillende sorteervolgorde zien voor de country
, first_name
, etc. sleutels in de JSON, als je de bovenstaande code een paar keer uitvoert. De meeste webservices die JSON uitvoeren, zullen een vaste sorteervolgorde afdwingen via een schema, wat het zeker gemakkelijker maakt om de JSON te lezen en te navigeren.
Werken met geneste arrays en codeerbare
Tot nu toe hebben we gewerkt met eenvoudige JSON-objecten – gewoon een klasse met een paar eigenschappen. Maar wat als de gegevens waarmee je werkt complexer zijn?
Bijv. de JSON gegevens die je terugkrijgt van een Twitter of Facebook webservice zijn vaak genest. Dat wil zeggen, de gegevens die je wilt bekijken zijn begraven in 2-3 niveaus van JSON arrays en dictionaries. Wat nu?
Laten we eerst naar een eenvoudig voorbeeld kijken. Hier is de Swift struct waar we mee gaan werken:
struct User: Codable { var first_name: String var last_name: String}
Daarna, hier is de JSON data die we willen decoderen:
let jsonString = """"""
Als je goed kijkt, zie je dat de data die we nodig hebben is opgeslagen als een array. De JSON is niet één simpel object, maar het is een array met 3 User
objecten. We weten dat het een array van objecten is, vanwege de vierkante haakjes (array) en de tilde haakjes
{ }
(object). Meervoudige items worden altijd gescheiden door komma’s.
Hoe kunnen we deze User
objecten decoderen?
let jsonData = jsonString.data(using: .utf8)!let users = try! JSONDecoder().decode(.self, from: jsonData)for user in users { print(user.first_name)}
Het bovenstaande codefragment lijkt sterk op wat je eerder hebt gezien, maar er is een belangrijk verschil. Zie je het type dat we aan de decode(_:from:)
functie geven? Het type is (met .self), oftewel “array van gebruikersobjecten”. In plaats van te werken met één
User
object, willen we er een heleboel decoderen, en dat wordt aangeduid met het array type.
Nog meer, we gaan het hebben over hoe je kunt werken met meer complexe geneste types. Stel bijvoorbeeld dat de array van User
objecten genest is in een JSON dictionary. Zoals dit:
let jsonString = """{ "users": }"""
In de bovenstaande JSON snippet, is het top-level element een dictionary (of “object”). Het heeft slechts één sleutel-waarde paar: een users
matrix. De gegevens die we willen, staan in die matrix. Hoe komen we er bij?
Het is belangrijk om onze aanpak niet te ingewikkeld te maken. Het is makkelijk om te denken dat je geavanceerde JSON parsing nodig hebt, maar het blijkt dat we eigenlijk ook Swift structs kunnen nesten. We gaan de hele JSON beschrijven als een struct, met de User
struct er in.
Hier, bekijk dit eens:
struct Response: Codable{ struct User: Codable { var first_name: String var last_name: String } var users: }
Hier is wat er aan de hand is:
- Het top-level JSON woordenboek komt overeen met het
Response
type. Net als voorheen voldoet dit type aanCodable
, ter ondersteuning van JSON codering en decodering. - Binnen die struct hebben we nog een struct gedefinieerd, genaamd
User
. Dit is exact hetzelfde type dat we eerder hebben gebruikt. Deze struct is “genest”. - De
Response
struct heeft een eigenschap genaamdusers
van het type, oftewel een array van gebruikersobjecten.
Het leuke is dat semantisch gezien, zowel de JSON als de Swift datastructuren precies hetzelfde zijn. Ze gebruiken alleen een andere syntaxis. We hebben een geneste array binnen de top-level dictionary gedefinieerd, net zoals de Response
struct een geneste User
struct en users
property binnen zich heeft.
Het laten werken is nu een fluitje van een cent. Kijk hier eens naar:
let jsonData = jsonString.data(using: .utf8)!let response = try! JSONDecoder().decode(Response.self, from: jsonData)for user in response.users { print(user.first_name)}
Zie je hoe we het Response
type gebruiken om de JSON te decoderen, en dan een lus maken over de response.users
property? Controleer dat met de Response
struct, en de JSON. We pakken het users
sleutel-waarde paar in het top-level woordenboek, en mappen de objecten erin naar User
objecten. Netjes!
Het gebruik van geneste types is een geweldige algemene aanpak voor complexe JSON-gegevensstructuren. Wanneer u een stukje JSON tegenkomt dat u niet gemakkelijk kunt weergeven in een tastbaar type, zoals User
of Tweet
, probeer het type dan uit te breiden tot iets als Response
of UserCollection
. In plaats van dieper te gaan, ga breder, en neem de volledige JSON op. Gebruik geneste types om bij de gegevens te komen die u wilt. Het is ook de moeite waard om hier op te merken dat je de User
struct niet hoeft toe te voegen aan de Response
struct – het kan er ook buiten.
Wordt aangenomen als iOS-ontwikkelaar
Leer iOS 14-apps bouwen met Swift 5
Schrijf je in voor mijn cursus iOS-ontwikkeling, en leer hoe je je carrière als professionele iOS-ontwikkelaar kunt beginnen.
Verder lezen
En dat is alles! U weet het nu:
- Hoe u
Codable
gebruikt om objecten te maken die gecodeerd en gedecodeerd kunnen worden - Hoe u
JSONDecoder
enJSONEncoder
gebruikt om JSON objecten en hun Swift tegenhangers te coderen en te decoderen - Waar coderen en decoderen voor is, en waarom het belangrijk is in de dagelijkse iOS ontwikkeling
- Hoe te werken met meer complexe JSON data, en hoe geneste types te gebruiken
Wilt u meer leren? Bekijk deze bronnen:
- Arrays, Dictionaries en Structs
- Werken met JSON in Swift met SwiftyJSON
- Inleiding tot objectgeoriënteerd programmeren in Swift
- How To: Een item in een matrix vinden in Swift
- Hoe Apple’s documentatie voor ontwikkelaars te gebruiken voor plezier en winst
- Hoe tekenreeksen te vinden met reguliere expressies in Swift
- Waarom App Architecture Matters
- Strings in Swift Uitgelegd
- Werken met bestanden op iOS met Swift
- Opslaan van gegevens met NSCoding en NSKeyedArchiver