Munka Codable és JSON a Swiftben

Written by Reinder de Vries on január 2021 2021 in App Development, iOS, Swift

Working with Codable and JSON in Swift

Az Codablet használhatod a Swiftben az egyéni adatformátumok, például a JSON kódolására és dekódolására natív Swift objektumokba. Hihetetlenül egyszerű a Swift objektumok leképezése JSON adatokra, és fordítva, egyszerűen az Codable protokoll elfogadásával.

Pragmatikus iOS-fejlesztőként inkább előbb, mint utóbb találkozni fogsz a JSON-nal. Minden webszolgáltatás, a Facebooktól a Foursquare-ig, a JSON formátumot használja az adatoknak az alkalmazásodba és az alkalmazásodból történő továbbítására. Hogyan tudod hatékonyan kódolni és dekódolni ezeket a JSON adatokat Swift objektumokba?

Ezzel a bemutatóval megtanulod, hogyan dolgozhatsz JSON objektumokkal Swiftben, a Codable protokoll segítségével. Amit megtanulsz, könnyen alkalmazható más adatformátumokra is. Elmélyedünk a JSONEncoder és JSONDecoder kódolásában és dekódolásában, és megmutatom, hogyan közvetíthetsz a JSON és a Swift adatok között.

Készen állsz? Gyerünk.

  1. Kezdjük el: Kódolás és dekódolás
  2. A Codable protokoll magyarázata
  3. Munka a Codable-vel
  4. A JSON dekódolása Swift objektumokká a Codable-vel
  5. A Swift objektumok kódolása JSON-ként a Codable-vel
  6. Munka a beágyazott tömbökkel és a Codable-vel
  7. További olvasmányok

Kezdjünk bele: Kódolás és dekódolás

Milyen problémát old meg valójában a Codable protokoll a Swiftben? Kezdjük egy példával.

Tegyük fel, hogy egy receptalkalmazást készítesz. Az alkalmazás különböző recepteket jelenít meg egy listában, beleértve a hozzávalókat, az utasításokat és az ételekre vonatkozó alapvető információkat.

Az adatokat az alkalmazáshoz egy webszolgáltatásból, illetve azok felhőalapú API-jából szerzed be. Ez az API a JSON adatformátumot használja. Minden alkalommal, amikor egy receptet kér a webszolgáltatástól, JSON-adatokat kap vissza.

Itt egy példa egy recept JSON-adataira:

{ "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!"}

Nézze meg a JSON-adatok szerkezetét.

A JSON-objektumok szögletes zárójelekbe { és }, a tömbök pedig szögletes zárójelekbe vannak csomagolva. A tulajdonságnevek karakterláncok, idézőjelekbe " csomagolva. A JSON értékek lehetnek karakterláncok, számok (idézőjelek nélkül), tömbök vagy más objektumok. Az adatok egymásba is fészkelhetők, azaz tömbök tömbökben, objektumok tömbökben stb. az adatok összetett hierarchiájának létrehozásához.

A JSON egy szövegalapú adatformátum, amelyet számos webszolgáltatás használ, beleértve a Twitter, a Facebook, a Foursquare stb. API-jait. Ha olyan alkalmazásokat készít, amelyek webalapú erőforrásokat használnak, bele fog futni a JSON-ba.

A JSON formátum jobb, mint az XML, egy gyakori alternatíva, mert hatékony, könnyen elemezhető és ember által is olvasható. A JSON elfogadott formátum a webszolgáltatások, API-k és alkalmazások számára. Az egész weben, alkalmazásokban és online szolgáltatásokban használják, mert a formátum egyszerű és rugalmas.

A JSON-nak pedig van egy kiváló képessége: bármilyen adatformátumot kódolhatunk JSON-ba, és a JSON-t dekódolhatjuk vissza bármilyen adatformátumba. Ez a kódolási és dekódolási folyamat teszi a JSON-t olyan erőteljessé.

Veheti a Swift String, Int, Double, URL, Date, Data, Array és Dictionary értékeit, és kódolhatja őket JSON-ként. Ezután elküldi őket a webszolgáltatásnak, amely dekódolja az értékeket az általa értett natív formátumba. Hasonlóképpen, a webszolgáltatás JSON-kódolt adatokat küld az alkalmazásodnak, és te dekódolod az adatokat natív típusokká, például String, Double és Array.

Amikor a receptalkalmazásod megkapja a JSON-t (lásd fent), akkor az dekódolható egy Swift struktúrává, mint például ez:

struct Recipe { var name: String var author: String var url: URL var yield: Int var ingredients: var instructions: String}

A Swiftben a Codable protokollt használjuk arra, hogy egy JSON adatobjektumból egy tényleges Swift osztály vagy struktúra legyen. Ezt nevezzük dekódolásnak, mert a JSON adatot dekódoljuk egy olyan formátumba, amelyet a Swift megért. A másik irányba is működik: a Swift objektumok JSON-ként való kódolása.

A Codable protokoll a Swiftben valójában a Decodable és Encodable protokollok alias változata. Mivel a kódolást és a dekódolást gyakran együtt használjuk, a Codable protokollt használjuk, hogy mindkét protokollt egy menetben kapjuk meg.

A kódolási/dekódolási munkafolyamat központi eleme a Swift Codable protokollja. A következőkben nézzük meg, hogyan működik!

Nem tudod megkülönböztetni a “kódolást” és a “dekódolást”? Gondolj erre a következőképpen: Adatokat alakítunk át “kódból” és “kóddá”, mint egy Enigma gép vagy egy titkos rejtjelező. A kódolás azt jelenti, hogy az adatokat kóddá alakítjuk; en-kódolás, vagy “kódon belül/belül”. A dekódolás azt jelenti, hogy kódot alakítunk át adatokká; dekódolás, vagy “kódból/kódból”.

Felvesznek iOS-fejlesztőként

Tanulj iOS 14 alkalmazásokat készíteni a Swift 5 segítségével

Iratkozz fel iOS-fejlesztői tanfolyamomra, és tanuld meg, hogyan kezdhetsz profi iOS-fejlesztőként karriert.

A kódolható protokoll magyarázata

A JSON használata a Swift 4 előtt egy kicsit PITA volt. A JSON-t magunknak kellett a JSONSerialization segítségével szerializálni, majd a JSON minden tulajdonságát a megfelelő Swift-típusba típusba önteni.

let json = try? JSONSerialization.jsonObject(with: data, options: )if let recipe = json as? { if let yield = recipe as? Int { recipeObject.yield = yield }}

A fenti részlet csak egy yield értéket ragad ki a JSON-ból, és Int-be önti. Ez rendkívül bőbeszédű, és nehéz megfelelően reagálni az esetleges hibákra és típuseltérésekre. Bár működik, nem ideális.

A SwiftyJSON-hoz hasonló könyvtárak jelentősen megkönnyítik a JSON-nal való munkát, de a JSON-adatokat még mindig le kell képezni a megfelelő Swift objektumokhoz és tulajdonságokhoz.

A Swift 4 és későbbi verziókkal a Codable protokollt használhatjuk helyette. A Swift struktúrádnak vagy osztályodnak csupán el kell fogadnia az Codable protokollt, és máris megkapod a JSON kódolást és dekódolást, ingyen.

A Codable protokoll két protokoll, a Decodable és az Encodable kompozíciója. Mindkét protokoll elég minimális; csak a init(from: Decoder) és encode(to: Encoder) függvényeket definiálják. Más szóval ezek a függvények azt jelentik, hogy ahhoz, hogy egy típus “dekódolható” vagy “kódolható” legyen, “dekódolni kell valamiből” és “kódolni kell valamibe”.

A Codable igazi varázsa a Decoder és Encoder protokollokkal történik. Ezeket a protokollokat a különböző adatformátumokat, például a JSON-t kódoló/dekódoló komponensek fogadják el. A Swiftben van néhány -kódoló:

  • PropertyListEncoder és PropertyListDecoder a .plist tulajdonságlistákhoz
  • JSONEncoder és JSONDecoder a JSON-hoz – ezek vagyunk mi!
  • Az NSKeyedArchiver a Codable, PropertyListEncoder, Data és NSCoding

A JSONDecoder és JSONEncoder osztályok a Decoder és Encoder protokollokat használják a JSON dekódolásához/dekódolásához. Ez azt is jelenti, hogy a Codable számára írhatsz saját egyedi kódolót/dekódolót, feltéve, hogy elfogadod a Decoder és Encoder protokollokat!

Kell egy kis felfrissítés a Swift protokollokról? Olvassa el a Protocols In Swift Explained című részt, hogy felzárkózzon.

Working with Codable

Nézzünk egy példát. Néhány JSON adatot fogunk leképezni egy Swift struktúrára. Lényegében dekódoljuk a JSON-t egy tényleges Swift objektummá.

Először létrehozunk egy User nevű struktúrát. Így:

struct User: Codable { var first_name: String var last_name: String var country: String}

A User strukt három egyszerű, String típusú tulajdonsággal rendelkezik, és megfelel az Codable protokollnak.

Ezt követően írjunk egy kis JSON-t. Ezzel a JSON-nal fogunk dolgozni:

{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}

A JSON-adatok jellemzően egy webservice-kérés válaszaként vagy egy .json fájlon keresztül kerülnek az alkalmazásunkba, de ebben a példában a JSON-t egyszerűen egy String-ba tesszük. Így:

let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""

Megjegyzés: A fenti kód a többsoros karakterláncok létrehozásához a """ hármas idézőjelet használja. Ügyes!

A következő lépésünk az, hogy dekódoljuk a JSON-t, és egy User objektummá alakítjuk. Így:

let jsonData = jsonString.data(using: .utf8)!let user = try! JSONDecoder().decode(User.self, from: jsonData)print(user.last_name)// Output: Doe

Íme, mi történik:

  • Először is, a jsonString-et egy Data objektummá alakítjuk a data(using:) függvény meghívásával a karakterláncon. Ez egy szükséges köztes lépés.
  • Ezután létrehozol egy JSONDecoder objektumot, és azonnal meghívod rajta a decode(_:from:) függvényt. Ez a JSON dekódolásával a jsonData-t egy User típusú objektummá alakítja. A User.self típust paraméterként adjuk meg.
  • Végül a print(user.last_name) segítségével kiírjuk a felhasználó vezetéknevét. Ennek az user értéknek User a típusa, tehát ez egy valódi Swift objektum!

Egyszerű, igaz? Lényegében “leképezted” a JSON objektumot egy Swift struktúrára, és dekódoltad a JSON formátumot egy natív objektummá, amivel a Swift tud dolgozni.

A fenti kódban figyelmen kívül hagyjuk a decode(_:from:) által try!-vel dobott hibákat. A saját kódodban a hibákat egy do-try-catch blokkal kell kezelned. A dobható hiba például az érvénytelen JSON megadásából adódik.

A User.self egy metatípus, egy hivatkozás magára a User típusra. Megmondjuk a dekódolónak, hogy az adatokat a User struct-tal akarjuk dekódolni, azzal, hogy megadjuk neki a típusra való hivatkozást.

A Codable típus definiálásakor gyakori hiba, mint például a User struct, a kulcsok és tulajdonságok össze nem illesztése. Ha olyan tulajdonságot adtál hozzá a struktúrádhoz, amely nincs jelen a JSON-ban, vagy más típusú, akkor hibát kapsz a JSON dekódolásakor. Az ellenkezője, azaz egy olyan tulajdonság, amely nem létezik a struktúrádban, de a JSON-ban igen, nem jelent problémát. A legegyszerűbb ezt a hibakeresést tulajdonságonként elvégezni, azaz folyamatosan hozzáadni tulajdonságokat, amíg a JSON már nem dekódolja hiba nélkül. A CodingKeys enummal (lásd alább) azt is meghatározhatjuk, hogy milyen tulajdonságokat/kulcsokat vegyünk fel vagy hagyjunk figyelmen kívül.

JSON dekódolása Swift objektumokká a Codable-vel

Nézzük meg még egyszer az előző fejezetből vett példát, és bővítsük ki. Itt van a JSON, amivel dolgozunk:

let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""

Aztán ezt átalakítjuk egy Data objektummá. Így:

let jsonData = jsonString.data(using: .utf8)!

Ez egy szükséges köztes lépés. Ahelyett, hogy a JSON-t sztringként ábrázolnánk, most a JSON-t natív Data objektumként tároljuk. A karakterkódolás, amit ehhez a karakterlánchoz használunk, az UTF8.

Ha jobban megnézzük, láthatjuk, hogy a fenti kód force unwrappinget használ, hogy a data(using:) opcionális visszatérési értékével dolgozzon. Csomagoljuk ki ezt az opcionális értéket elegánsabban!

if let jsonData = jsonString.data(using: .utf8) { // Use `jsonData`} else { // Respond to error }

A fenti kóddal tudunk reagálni a hibákra, amikor a data(using:) nil értéket ad vissza. Megjeleníthetünk például egy hibaüzenetet, vagy némán hagyhatjuk, hogy a feladat meghiúsuljon, és elmenthetjük a diagnosztikai információkat egy naplóba.

A következőkben létrehozunk egy új JSONDecoder objektumot. Így:

let decoder = JSONDecoder()

Ezt a dekódolót használjuk ezután a JSON adatok dekódolására. Így:

let user = try! decoder.decode(User.self, from: jsonData)

Az decode(_:from:) függvény azonban hibát dobhat. A fenti kód összeomlik, amikor ez megtörténik. Ismétlem, reagálni akarunk az esetlegesen bekövetkező hibákra. Így:

do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name)} catch { print(error.localizedDescription)}

És az egész kódblokk most a következőképpen néz ki. Látja, hogy ez mennyire más?

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) }}

Soha ne hallgassuk el a hibákat. Kapjuk el a hibát, és reagáljunk rá, akár UI/UX-szal, a feladat megismétlésével, akár naplózással, összeomlással és javítással.

Munka a CodingKeys-szel

Mi van, ha a JSON tulajdonságaink, például first_name és/vagy firstName, eltérnek a Swift struct tulajdonságaitól? Itt jön a képbe a CodingKeys.

Minden osztály vagy struktúra, amely megfelel a Codable-nak, deklarálhat egy speciális, CodingKeys nevű beágyazott felsorolást. Ezt használjuk a kódolandó és dekódolandó tulajdonságok definiálására, beleértve a nevüket is.

Nézzünk egy példát. Az alábbi User struktúrában a tulajdonságneveket snake_case-ről camelCase-re változtattuk. Például a first_name kulcsot mostantól firstName-nek hívjuk.

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 }}

Ha a fenti struktúrát az előző szakaszok példáival együtt használjuk, látni fogjuk, hogy egy User objektumot használhatunk az új tulajdonságnevekkel. Így:

print(user.firstName)// Output: Johnprint(user.country)// Output: United Kingdom

A CodingKeys felsorolás az eseteit tulajdonságokra képezi le, és a karakterláncértékek segítségével kiválasztja a megfelelő tulajdonságneveket a JSON-adatokban. Az enumban szereplő eset a struktúrában használni kívánt tulajdonság neve, az értéke pedig a kulcs neve a JSON-adatokban.

Swift objektumok kódolása JSON-ként a Codable-vel

Eleddig a JSON-adatok Swift objektumokká történő dekódolására koncentráltunk. Mi a helyzet a másik irányban? Az objektumokat is kódolhatjuk JSON-ként? Igen, miért ne!

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)

A kimenet pedig:

{"country":"Cryptoland","first_name":"Bob","last_name":"and Alice"}

Mi történik itt?

  • Először létrehozunk egy User objektumot, és hozzárendelünk néhány értéket a tulajdonságaihoz
  • Majd a encode(_:) segítségével kódoljuk a user objektumot egy JSON Data objektumba
  • Végül, átalakítod az Data objektumot String-ba és kinyomtatod

Ha jobban megnézed, láthatod, hogy a kódolás ugyanazokat a lépéseket követi, mint a dekódolás, csak fordítva. A Swift objektumtól a dekóderen keresztül haladva egy JSON sztringet eredményez.

Mint korábban, kibővíthetjük a példát a hibák kezelésére? Igen! Így:

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)}

A fenti példában a kódoló outputFormatting tulajdonságát is használjuk a JSON adatok “szépen kiírására”. Ez szóközöket, tabulátorokat és újsorokat ad hozzá, hogy a JSON karakterlánc könnyebben olvasható legyen. Így:

{ "country" : "Cryptoland", "first_name" : "Bob", "last_name" : "and Alice"}

Félelmetes!

Azért érdemes megjegyezni, hogy a Swiftben és a JSON-ban a szótáraknak nincs rögzített rendezési sorrendje. Ezért a JSON-ban a country, first_name stb. kulcsoknál eltérő rendezési sorrendet fogsz látni, ha a fenti kódot néhányszor lefuttatod. A legtöbb JSON-t kimenő webszolgáltatás egy sémán keresztül kikényszerít egy fix rendezési sorrendet, ami határozottan megkönnyíti a JSON olvasását és navigálását.

Munka beágyazott tömbökkel és Codable

Eleddig egyszerű JSON objektumokkal dolgoztunk – csak egy osztály néhány tulajdonsággal. De mi van akkor, ha az adatok, amelyekkel dolgozunk, összetettebbek?

A JSON-adatok, amelyeket például egy Twitter vagy Facebook webszolgáltatásból kapunk vissza, gyakran egymásba ágyazottak. Ez azt jelenti, hogy a kívánt adatok 2-3 szintű JSON tömbök és szótárak között vannak eltemetve. Most mi legyen?

Lássunk először egy egyszerű példát. Itt van a Swift struktúra, amivel dolgozni fogunk:

struct User: Codable { var first_name: String var last_name: String}

Ezután itt vannak a JSON adatok, amiket dekódolni szeretnénk:

let jsonString = """"""

Ha jobban megnézzük, láthatjuk, hogy a nekünk szükséges adatok tömbként vannak tárolva. A JSON nem egy egyszerű objektum, hanem egy 3 User objektumot tartalmazó tömb. Az (tömb) és a { } (objektum) szögletes zárójelekből tudjuk, hogy ez egy objektumokból álló tömb. A több elemet mindig vesszővel választjuk el.

Hogyan tudjuk dekódolni ezeket a User objektumokat? Íme, hogyan:

let jsonData = jsonString.data(using: .utf8)!let users = try! JSONDecoder().decode(.self, from: jsonData)for user in users { print(user.first_name)}

A fenti kódrészlet rendkívül hasonló az eddig látottakhoz, de van egy fontos különbség. Látod, milyen típust adunk meg a decode(_:from:) függvénynek? A típus (.self-el), vagyis “felhasználói objektumok tömbje”. Ahelyett, hogy egyetlen User objektummal dolgoznánk, egy csomót akarunk dekódolni, és ezt a tömbtípussal jelöljük.

A következőkben arról fogunk beszélni, hogyan dolgozhatunk összetettebb, egymásba ágyazott típusokkal. Vegyük például, hogy a User objektumok tömbje egy JSON szótárba van beágyazva. Így:

let jsonString = """{ "users": }"""

A fenti JSON részletben a legfelső szintű elem egy szótár (vagy “objektum”). Csak egy kulcs-érték párja van: egy users tömb. A kívánt adatok ebben a tömbben vannak. Hogyan jutunk hozzá?

Fontos, hogy a megközelítésünk ne legyen túl bonyolult. Könnyű azt gondolni, hogy valami fejlett JSON-elemzésre lesz szükségünk, de mint kiderült, valójában Swift-struktúrákat is be tudunk fészkelni. A teljes JSON-t egy struktúraként fogjuk leírni, benne a User struktúrával.

Nézzük meg ezt:

struct Response: Codable{ struct User: Codable { var first_name: String var last_name: String } var users: }

Íme, mi történik:

  • A legfelső szintű JSON-szótár a Response típusnak felel meg. Csakúgy, mint korábban, ez a típus megfelel a Codable-nak, hogy támogassa a JSON kódolást és dekódolást.
  • A struktúrán belül definiáltunk egy másik struktúrát, amelynek a neve User. Ez pontosan ugyanaz a típus, amit már korábban is használtunk. Ez a struktúra “beágyazott”.
  • A Response struktúrának van egy users nevű, típusú tulajdonsága, vagyis felhasználói objektumok tömbje.

A király dolog az, hogy szemantikailag mind a JSON, mind a Swift adatstruktúra pontosan ugyanaz. Csak más szintaxist használnak. Egy beágyazott tömböt definiáltunk a legfelső szintű szótáron belül, ahogyan a Response struktúrának is van egy beágyazott User struktúrája és egy users tulajdonsága benne.

Az, hogy ez működjön, már gyerekjáték. Ezt nézd meg:

let jsonData = jsonString.data(using: .utf8)!let response = try! JSONDecoder().decode(Response.self, from: jsonData)for user in response.users { print(user.first_name)}

Látszik, hogy a Response típust használjuk a JSON dekódolásához, majd a response.users tulajdonságon loopolunk? Ellenőrizd ezt a Response struktúrával, és a JSON-nal. Kiválasztjuk a users kulcs-érték párt a legfelső szintű szótárból, és a benne lévő objektumokat User objektumokra képezzük le. Ügyes!

A beágyazott típusok használata nagyszerű általános célú megközelítés az összetett JSON adatstruktúrákhoz. Ha olyan JSON-darabkára bukkansz, amelyet nem tudsz könnyen ábrázolni egy kézzelfogható típusban, mint például User vagy Tweet, próbáld meg kibővíteni a típust valami olyasmire, mint Response vagy UserCollection. Ahelyett, hogy mélyebbre mennél, menj szélesebbre, és építsd be a teljes JSON-t. Használjon egymásba ágyazott típusokat, hogy eljusson a kívánt adatokhoz. Itt is érdemes megjegyezni, hogy nem kell a User struktúrát a Response struktúrába illesztened – kívül is mehet rajta.

Fogadj fel iOS-fejlesztőként

Tanulj meg iOS 14 alkalmazásokat készíteni a Swift 5 segítségével

Iratkozz fel iOS-fejlesztői tanfolyamomra, és tanuld meg, hogyan kezdhetsz profi iOS-fejlesztőként karriert.

További olvasnivalók

És ez minden! Most már tudod:

  • Hogyan használd a Codable-t kódolható és dekódolható objektumok létrehozásához
  • Hogyan használd a JSONDecoder és JSONEncoder-t JSON objektumok és Swift megfelelőik kódolásához és dekódolásához
  • Mire való a kódolás és dekódolás, és miért fontos a mindennapi iOS-fejlesztésben
  • Hogyan dolgozzunk összetettebb JSON-adatokkal, és hogyan használjuk az egymásba ágyazott típusokat

Még többet szeretne megtudni? Nézze meg ezeket a forrásokat:

  • Tömbök, szótárak és struktúrák
  • Munka JSON-nal a Swiftben a SwiftyJSON-nal
  • A tárgyközpontú programozás bevezetése a Swiftben
  • How To: Hogyan keressünk egy elemet egy tömbben Swiftben
  • How To Use Apple’s Developer Documentation For Fun And Profit
  • How To Find Strings With Regular Expressions In Swift
  • Why App Architecture Matters
  • Strings in Swift Explained
  • Working with Files on iOS with Swift
  • Storing Data with NSCoding and NSKeyedArchiver

Szólj hozzá!