Práce s Codable a JSON ve Swiftu

Napsal Reinder de Vries dne 20. ledna 2021 v rubrice Vývoj aplikací, iOS, Swift

Práce s Codable a JSON ve Swiftu

Pomocí Codable můžete ve Swiftu kódovat a dekódovat vlastní datové formáty, například JSON, na nativní objekty Swiftu. Je neuvěřitelně snadné mapovat objekty Swiftu na data JSON a naopak, stačí přijmout protokol Codable.

Jako pragmatický vývojář iOS se s JSON setkáte spíše dříve než později. Každá webová služba, od Facebooku po Foursquare, používá k přenosu dat do aplikace a z ní formát JSON. Jak můžete tato data JSON efektivně kódovat a dekódovat do objektů Swift?“

V tomto kurzu se naučíte pracovat s objekty JSON ve Swiftu pomocí protokolu Codable. To, co se naučíte, lze snadno aplikovat i na jiné datové formáty. Dostaneme se ke kódování a dekódování pomocí JSONEncoder a JSONDecoder a ukážu vám, jak zprostředkovat mezi daty JSON a Swift.

Připraveni? Jdeme na to.

  1. Začínáme: Kódování a dekódování
  2. Vysvětlení protokolu Codable
  3. Práce s Codable
  4. Dekódování JSON do objektů Swift pomocí Codable
  5. Kódování objektů Swift jako JSON pomocí Codable
  6. Práce s vnořenými poli a Codable
  7. Další čtení

Začínáme: Kódování a dekódování

Jaký problém vlastně řeší protokol Codable ve Swiftu? Začněme příkladem.

Představte si, že vytváříte aplikaci s recepty. Aplikace zobrazuje různé recepty v seznamu, včetně ingrediencí, návodu a základních informací o jídle.

Data pro aplikaci získáváte z webové služby a jejich cloudového rozhraní API. Toto rozhraní API používá datový formát JSON. Pokaždé, když si vyžádáte recept z webové služby, dostanete zpět data JSON.

Tady je příklad dat JSON pro 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!"}

Podívejte se na strukturu dat JSON.

Objekty JSON jsou zabaleny do hranatých závorek { a } a pole jsou zabalena do hranatých závorek . Názvy vlastností jsou řetězce zabalené do uvozovek ". Hodnoty v JSON mohou být řetězce, čísla (bez uvozovek), pole nebo jiné objekty. Data lze také vnořovat, tj. pole do polí, objekty do polí atd. a vytvářet tak složitou hierarchii dat.

JSON je textový formát dat, který používá mnoho webových služeb, včetně rozhraní API společností Twitter, Facebook, Foursquare atd. Pokud vytváříte aplikace, které používají webové zdroje, setkáte se s JSON.

Formát JSON je lepší než XML, běžná alternativa, protože je efektivní, snadno se analyzuje a je čitelný pro člověka. JSON je dohodnutý formát pro webové služby, rozhraní API a aplikace. Používá se na celém webu, v aplikacích a online službách, protože tento formát je jednoduchý a flexibilní.

A JSON má jednu vynikající schopnost: do JSON můžete zakódovat jakýkoli datový formát a zpětně dekódovat JSON do jakéhokoli datového formátu. Právě díky tomuto procesu kódování a dekódování je JSON tak výkonný.

Můžete vzít hodnoty Swift String, Int, Double, URL, Date, Data, Array a Dictionary a zakódovat je jako JSON. Poté je odešlete webové službě, která hodnoty dekóduje do nativního formátu, kterému rozumí. Podobně webová služba odešle data zakódovaná jako JSON vaší aplikaci a vy je dekódujete do nativních typů, jako jsou String, Double a Array.

Když vaše aplikace s receptem obdrží JSON (viz výše), lze jej pak dekódovat do struktury Swift, jako je tato:

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

V systému Swift se protokol Codable používá k přechodu z datového objektu JSON na skutečnou třídu nebo strukturu Swift. Tomu se říká dekódování, protože data JSON jsou dekódována do formátu, kterému Swift rozumí. Funguje to i opačně: kódování objektů Swift jako JSON.

Protokol Codable ve Swiftu je vlastně alias protokolů Decodable a Encodable. Protože často používáte kódování a dekódování společně, používáte protokol Codable, abyste získali oba protokoly najednou.

Ústředním prvkem pracovního postupu kódování/dekódování je protokol Codable ve Swiftu. Pojďme zjistit, jak funguje, dále!

Neumíte rozlišit „kódování“ a „dekódování“? Představte si to takto: Převádíme data z a do „kódu“, jako stroj Enigma nebo tajná šifra. Kódování znamená převádění dat na kód; en-kódování neboli „v kódu“. Dekódování znamená převod kódu na data; de-kódování neboli „z/do kódu“.

Získejte práci jako vývojář iOS

Naučte se vytvářet aplikace pro iOS 14 pomocí Swiftu 5

Přihlaste se na můj kurz vývoje iOS a naučte se, jak začít kariéru profesionálního vývojáře iOS.

Vysvětlení protokolu Codable

Používání JSON před Swiftem 4 bylo trochu PITA. Museli jste si sami serializovat JSON pomocí JSONSerialization a pak každou vlastnost JSONu převést na správný typ Swiftu.

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

Výše uvedený úryvek pouze vezme z JSONu hodnotu yield a převede ji na Int. Je to extrémně mnohomluvné a je těžké správně reagovat na případné chyby a nesrovnalosti typu. I když to funguje, není to ideální.

Knihovny jako SwiftyJSON práci s JSON značně usnadňují, ale stále je třeba namapovat data JSON na příslušné objekty a vlastnosti Swiftu.

V systému Swift 4 a novějších verzích můžete místo toho použít protokol Codable. Vaše struktura nebo třída ve Swiftu musí pouze přijmout protokol Codable a získáte kódování a dekódování JSON zdarma.

Protokol Codable je složeninou dvou protokolů, Decodable a Encodable. Oba protokoly jsou poměrně minimální; definují pouze funkce init(from: Decoder), respektive encode(to: Encoder). Jinými slovy, tyto funkce znamenají, že aby byl typ „dekódovatelný“ nebo „kódovatelný“, bude muset „dekódovat z něčeho“ a „kódovat do něčeho“.

Skutečné kouzlo Codable se odehrává s protokoly Decoder a Encoder. Tyto protokoly přebírají komponenty, které kódují/dekódují různé datové formáty, například JSON. Ve Swiftu máme několik -kodérů:

  • PropertyListEncoder a PropertyListDecoder pro seznamy vlastností .plist
  • JSONEncoder a JSONDecoder pro JSON – to jsme my!
  • NSKeyedArchiver může pracovat s Codable, prostřednictvím PropertyListEncoder, Data a NSCoding

Třídy JSONDecoder a JSONEncoder používají tyto protokoly Decoder a Encoder k zajištění funkčnosti dekódování/enkódování JSON. To také znamená, že si můžete napsat vlastní kodér/dekodér pro Codable, pokud přijmete protokoly Decoder a Encoder!

Chcete si osvěžit protokoly ve Swiftu? Přečtěte si knihu Protokoly ve Swiftu s vysvětlením, abyste to dohnali.

Práce s kódovatelnými

Podívejme se na příklad. Budeme mapovat nějaká data JSON na strukturu Swift. V podstatě dekódujeme JSON na skutečný objekt Swift.

Nejprve vytvoříme strukturu s názvem User. Takto:

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

Strukt User má tři jednoduché vlastnosti typu String a odpovídá protokolu Codable.

Poté zapíšeme kousek JSON. Toto je JSON, se kterým budeme pracovat:

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

Data JSON obvykle vstupují do aplikace jako odpověď na požadavek webové služby nebo prostřednictvím souboru .json, ale pro tento příklad jednoduše vložíme JSON do String. Takto:

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

Poznámka: Výše uvedený kód používá trojité uvozovky """ pro vytvoření víceřádkových řetězců. Úhledné!“

Dalším krokem je dekódování JSON a jeho přeměna na objekt User. Takto:

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

Takto se děje:

  • Nejprve proměníme jsonString na objekt Data zavoláním funkce data(using:) na řetězec. To je nezbytný mezikrok.
  • Poté vytvoříte objekt JSONDecoder a ihned na něm zavoláte funkci decode(_:from:). Tím se z jsonData dekódováním JSONu stane objekt typu User. Typ User.self je uveden jako parametr.
  • Nakonec vypíšete příjmení uživatele pomocí print(user.last_name). Tato hodnota user má jako typ User, takže je to skutečný objekt Swift!“

Jednoduché, že? V podstatě jste „namapovali“ objekt JSON na strukturu Swift a dekódovali formát JSON na nativní objekt, se kterým může Swift pracovat.

V uvedeném kódu ignorujeme všechny chyby, které vyhodí decode(_:from:) s try!. Ve vlastním kódu budete muset chyby ošetřit pomocí bloku do-try-catch. Chyba, která může být vyhozena, pochází například z poskytnutí neplatného JSON.

Blok User.self je metatyp, odkaz na samotný typ User. Tím, že mu poskytneme odkaz na tento typ, říkáme dekodéru, že chceme dekódovat data pomocí struktury User.

Častou chybou při definování typu Codable, jako je struktura User, je nesoulad klíčů a vlastností. Pokud jste do struktury přidali vlastnost, která se v JSON nenachází nebo má jiný typ, dojde při dekódování JSON k chybě. Opačný případ, tj. vlastnost, která ve vaší struct neexistuje, ale v JSON ano, není problém. Nejjednodušší je ladit to po jedné vlastnosti, tj. přidávat vlastnosti tak dlouho, dokud se JSON přestane dekódovat bez chyb. Pomocí výčtu CodingKeys (viz níže) můžete také určit, které vlastnosti/klíče zahrnout a které ignorovat.

Dekódování JSON do objektů Swift pomocí Codable

Podívejme se znovu na příklad z předchozí části a rozšiřme jej. Zde je JSON, se kterým pracujeme:

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

Poté jej převedeme na objekt Data. Takto:

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

Jedná se o nezbytný mezikrok. Namísto reprezentace JSON jako řetězce nyní ukládáme JSON jako nativní objekt Data. Kódování znaků, které pro tento řetězec používáme, je UTF8.

Pokud se podíváte pozorně, uvidíte, že výše uvedený kód používá force unwrapping pro práci s nepovinnou návratovou hodnotou z data(using:). Pojďme tuto nepovinnou hodnotu rozbalit elegantněji!

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

Pomocí výše uvedeného kódu můžeme reagovat na chyby, když data(using:) vrátí nil. Mohli bychom například zobrazit chybové hlášení nebo nechat úlohu tiše selhat a uložit diagnostické informace do protokolu.

Dále vytvoříme nový objekt JSONDecoder. Takto:

let decoder = JSONDecoder()

Tento dekodér pak použijeme k dekódování dat JSON. Takto:

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

Funkce decode(_:from:) však může vyhodit chybu. Kdykoli k tomu dojde, výše uvedený kód spadne. Opět chceme reagovat na všechny chyby, které mohou nastat. Takto:

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

A celý blok kódu nyní vypadá následovně. Vidíte, jak se to liší?“

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

Nikdy nemlčte o chybách. Zachyťte chybu a reagujte na ni buď pomocí uživatelského rozhraní/UX, opakováním úlohy, nebo protokolováním, pádem a opravou.

Práce s kódovacími klíči

Co když jsou naše vlastnosti JSON, jako například first_name a/nebo firstName, jiné než vlastnosti struktur Swift? V tom případě přichází na řadu CodingKeys.

Každá třída nebo struktura, která vyhovuje Codable, může deklarovat speciální vnořený výčet nazvaný CodingKeys. Pomocí něj definujete vlastnosti, které mají být kódovány a dekódovány, včetně jejich názvů.

Podívejme se na příklad. V níže uvedené struktuře User jsme změnili názvy vlastností z snake_case na camelCase. Například klíč first_name se nyní jmenuje 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 }}

Při použití výše uvedené struktury s příklady v předchozích částech uvidíte, že můžete použít objekt User s novými názvy vlastností. Takto:

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

Výčet CodingKeys mapuje své případy na vlastnosti a používá řetězcové hodnoty pro výběr správných názvů vlastností v datech JSON. Případ ve výčtu je název vlastnosti, kterou chcete použít ve struktuře, a jeho hodnota je název klíče v datech JSON.

Kódování objektů Swift jako JSON pomocí Codable

Dosud jsme se zaměřili na dekódování dat JSON do objektů Swift. Co takhle jít opačným směrem? Můžeme také kódovat objekty jako JSON? Ano, proč 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 výstup je:

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

Co se zde stane?

  • Nejprve vytvoříte objekt User a přiřadíte jeho vlastnostem nějaké hodnoty
  • Poté pomocí encode(_:) zakódujete objekt user do objektu JSON Data
  • Nakonec, převedete objekt Data na String a vypíšete jej

Pokud se podíváte pozorně, zjistíte, že kódování probíhá stejným způsobem jako dekódování, jen v opačném pořadí. Prochází od objektu Swift, přes dekodér a výsledkem je řetězec JSON.

Stejně jako dříve, můžeme příklad rozšířit o řešení chyb? Ano! Takto:

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

V uvedeném příkladu také používáme vlastnost outputFormatting kodéru, abychom „hezky vytiskli“ data JSON. Tím se přidají mezery, tabulátory a nové řádky, aby se řetězec JSON lépe četl. Takto:

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

Úžasné!!!

Je třeba poznamenat, že slovníky ve Swiftu ani v JSON nemají pevně dané pořadí řazení. Proto pokud výše uvedený kód spustíte několikrát, uvidíte v JSONu různá pořadí řazení pro klíče country, first_name atd. Většina webových služeb, které vypisují JSON, si vynutí pevné pořadí řazení prostřednictvím schématu, což rozhodně usnadňuje čtení a orientaci v JSON.

Práce s vnořenými poli a kódovatelnými objekty

Dosud jsme pracovali s jednoduchými objekty JSON – pouze třídou s několika vlastnostmi. Ale co když jsou data, se kterými pracujete, složitější?

Například data JSON, která můžete získat zpět z webové služby Twitter nebo Facebook, jsou často vnořená. To znamená, že data, ke kterým se chcete dostat, jsou pohřbena ve 2-3 úrovních polí a slovníků JSON.

Nejprve se podívejme na jednoduchý příklad. Zde je struktura Swift, se kterou budeme pracovat:

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

Tady jsou data JSON, která chceme dekódovat:

let jsonString = """"""

Pokud se podíváte pozorně, uvidíte, že data, která potřebujeme, jsou uložena jako pole. JSON není jeden jednoduchý objekt, ale je to pole se třemi objekty User. Že se jedná o pole objektů, víme díky hranatým závorkám (pole) a hranatým závorkám { } (objekt). Více položek je vždy odděleno čárkami.

Jak můžeme tyto User objekty dekódovat? Zde je návod:

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

Výše uvedený úryvek kódu je velmi podobný tomu, co jste viděli dříve, ale je tu jeden důležitý rozdíl. Vidíte, jaký typ poskytujeme funkci decode(_:from:)? Typ je (s .self) neboli „pole objektů User“. Místo toho, abychom pracovali s jedním objektem User, chceme jich dekódovat několik, a to je označeno typem pole .

Příště probereme, jak můžete pracovat se složitějšími vnořenými typy. Uvažujme například, že pole objektů User je vnořené uvnitř slovníku JSON. Takto:

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

V uvedeném úryvku JSON je prvkem nejvyšší úrovně slovník (neboli „objekt“). Má pouze jeden pár klíč-hodnota: pole users. Data, která chceme, jsou uvnitř tohoto pole. Jak se k nim dostaneme?

Je důležité, aby náš přístup nebyl příliš složitý. Je snadné si myslet, že budeme potřebovat nějaké pokročilé parsování JSON, ale jak se ukázalo, ve skutečnosti můžeme vnořovat i struktury Swift. Celý JSON popíšeme jako strukturu, uvnitř které bude struktura User.

Tady se podívejte, co se děje:

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

Tady se děje:

  • Slovníku JSON nejvyšší úrovně odpovídá typ Response. Stejně jako předtím tento typ odpovídá Codable, aby podporoval kódování a dekódování JSON.
  • Uvnitř této struktury jsme definovali další strukturu s názvem User. Jedná se o naprosto stejný typ, jaký jsme používali dříve. Tato struktura je „vnořená“.
  • Struktura Response má jednu vlastnost nazvanou users typu , neboli pole objektů User.

Zajímavé je, že sémanticky jsou obě datové struktury JSON a Swift úplně stejné. Pouze používají jinou syntaxi. Definovali jsme vnořené pole uvnitř slovníku nejvyšší úrovně, stejně jako struktura Response má v sobě vnořenou strukturu User a vlastnost users.

Zprovoznění je teď hračka. Podívejte se na to:

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

Vidíte, jak používáme typ Response k dekódování JSONu a pak smyčku nad vlastností response.users? Zkontrolujte to pomocí struktury Response a JSON. Vybíráme dvojici klíč-hodnota users ve slovníku nejvyšší úrovně a mapujeme objekty uvnitř na objekty User. Úhledné!“

Používání vnořených typů je skvělý univerzální přístup ke složitým datovým strukturám JSON. Když narazíte na část JSON, kterou nemůžete snadno reprezentovat hmatatelným typem, například User nebo Tweet, zkuste typ rozšířit na něco jako Response nebo UserCollection. Místo toho, abyste šli do hloubky, jděte do šířky a začleňte kompletní JSON. Používejte vnořené typy, abyste se dostali k požadovaným datům. Zde také stojí za zmínku, že strukturu User nemusíte přidávat do struktury Response – může jít i mimo ni.

Získejte zaměstnání jako vývojář iOS

Naučte se vytvářet aplikace pro iOS 14 pomocí Swiftu 5

Zapište se do mého kurzu vývoje iOS a naučte se, jak začít kariéru profesionálního vývojáře iOS.

Další čtení

A to je vše! Nyní už víte:

  • Jak pomocí Codable vytvořit objekty, které lze kódovat a dekódovat
  • Jak pomocí JSONDecoder a JSONEncoder kódovat a dekódovat objekty JSON a jejich protějšky ve Swiftu
  • K čemu slouží kódování a dekódování, a proč je důležité při každodenním vývoji pro iOS
  • Jak pracovat se složitějšími daty JSON a jak používat vnořené typy

Chceš se dozvědět víc? Podívejte se na tyto zdroje:

  • Mole, slovníky a struktury
  • Práce s JSON ve Swiftu pomocí SwiftyJSON
  • Úvod do objektově orientovaného programování ve Swiftu
  • Jak na to: Jak najít položku v poli ve Swiftu
  • Jak používat vývojářskou dokumentaci Apple pro zábavu i zisk
  • Jak najít řetězce pomocí regulárních výrazů ve Swiftu
  • Proč? Na architektuře aplikací záleží
  • Vysvětlení řetězců ve Swiftu
  • Práce se soubory v iOS pomocí Swiftu
  • Ukládání dat pomocí NSCoding a NSKeyedArchiver

Podle čeho?

Napsat komentář