Napsal Reinder de Vries dne 20. ledna 2021 v rubrice Vývoj aplikací, iOS, Swift
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.
- Začínáme: Kódování a dekódování
- Vysvětlení protokolu Codable
- Práce s Codable
- Dekódování JSON do objektů Swift pomocí Codable
- Kódování objektů Swift jako JSON pomocí Codable
- Práce s vnořenými poli a Codable
- 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
aPropertyListDecoder
pro seznamy vlastností .plist -
JSONEncoder
aJSONDecoder
pro JSON – to jsme my! - NSKeyedArchiver může pracovat s
Codable
, prostřednictvímPropertyListEncoder
,Data
aNSCoding
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 objektData
zavoláním funkcedata(using:)
na řetězec. To je nezbytný mezikrok. - Poté vytvoříte objekt
JSONDecoder
a ihned na něm zavoláte funkcidecode(_:from:)
. Tím se zjsonData
dekódováním JSONu stane objekt typuUser
. TypUser.self
je uveden jako parametr. - Nakonec vypíšete příjmení uživatele pomocí
print(user.last_name)
. Tato hodnotauser
má jako typUser
, 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 objektuser
do objektu JSONData
- Nakonec, převedete objekt
Data
naString
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 nazvanouusers
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
aJSONEncoder
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?