Codable cheat sheet
Codable
byla jednou ze základních funkcí Swiftu 4.0 a přinesla neuvěřitelně hladký převod mezi datovými typy Swiftu a JSON. Ve Swiftu 4.1 se pak díky přidání nových funkcí ještě zlepšil a do budoucna očekávám ještě větší věci.
V tomto článku chci poskytnout rychlé ukázky kódu, které vám pomohou odpovědět na běžné otázky a vyřešit běžné problémy, a to vše pomocí Codable
.
SPONSORED Vytváření a udržování infrastruktury pro předplatné v aplikacích je náročné. Naštěstí existuje lepší způsob. S RevenueCatem můžete implementovat předplatné pro svou aplikaci v řádu hodin, ne měsíců, takže se můžete vrátit k tvorbě aplikace.
Zkuste to zdarma
Sponzorujte Hacking with Swift a oslovte největší komunitu uživatelů Swiftu na světě!
Kódování a dekódování JSONu
Začneme od základů: převedeme nějaký JSON na struktury Swift.
Nejprve tu máme nějaký JSON, se kterým můžeme pracovat:
let json = """"""let data = Data(json.utf8)
Poslední řádek jej převede na objekt Data
, protože s tím pracují dekodéry Codable
.
Dále musíme definovat strukturu Swift, která bude obsahovat naše hotová data:
struct User: Codable { var name: String var age: Int}
Teď můžeme pokračovat a provést dekódování:
let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}
Tím se vypíše „Paul“, což je jméno prvního uživatele v JSONu.
Konverze case
Častým problémem JSONu je, že pro své názvy klíčů používá jiné formátování, než chceme používat ve Swiftu. Například se může stát, že v JSONu dostanete „first_name“ a budete ho potřebovat převést na vlastnost firstName
.
Jedním zřejmým řešením je zde prostě změnit buď JSON, nebo vaše typy ve Swiftu tak, aby používaly stejnou konvenci pojmenování, ale to zde dělat nebudeme. Místo toho budu předpokládat, že máte takovýto kód:
let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}
Aby to fungovalo, musíme v našem dekodéru JSON změnit pouze jednu vlastnost:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase
To dává Swiftu pokyn, aby mapoval jména hadích písmen (names_written_like_this) na jména velbloudích písmen (namesWrittenLikeThis).
Mapování různých názvů klíčů
Pokud máte klíče JSON, které se zcela liší od vašich vlastností ve Swiftu, můžete je mapovat pomocí výčtu CodingKeys
.
Podívejte se na tento JSON:
let json = """"""
Tyto názvy klíčů nejsou nic moc a ve skutečnosti bychom chtěli tato data převést do struktury takto:
struct User: Codable { var firstName: String var lastName: String var age: Int}
Aby se tak stalo, musíme deklarovat CodingKeys
enum: mapování, které Codable
může použít k převodu názvů JSON na vlastnosti naší struktury. Jedná se o běžný enum, který pro své surové hodnoty používá řetězce, takže můžeme současně zadat jak název naší vlastnosti (případ enumu), tak název JSON (hodnotu enumu). Musí také vyhovovat protokolu CodingKey
, díky čemuž to funguje s protokolem Codable
.
Přidejte tedy tento enum do struct:
enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}
Ten bude nyní schopen dekódovat JSON podle plánu.
Poznámka: enum se jmenuje CodingKeys
a protokol se jmenuje CodingKey
.
Práce s daty ISO-8601
Na internetu existuje mnoho způsobů práce s daty, ale ISO-8601 je nejběžnější. Kóduje úplné datum ve formátu RRRR-MM-DD, dále písmeno „T“, které signalizuje začátek časové informace, pak čas ve formátu HH:MM:SS a nakonec časové pásmo. Časové pásmo „Z“, zkratka pro „čas Zulu“, se běžně používá pro označení UTC.
Codable
dokáže zpracovat ISO-8601 pomocí vestavěného převodníku data. Pokud tedy máme k dispozici tento JSON:
let json = """"""
Můžeme jej dekódovat takto:
struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601
To umožňuje analýzu data ISO-8601, která převede datum 1999-04-03T17:30:31Z na instanci Date
a zároveň zvládne převod hadího písmena na velbloudí.
Práce s dalšími běžnými daty
Swift je vybaven vestavěnou podporou tří dalších důležitých formátů data. Používáte je stejně jako data ISO-8601, jak je uvedeno výše, takže se o nich zmíním jen stručně:
- Formát
.deferredToDate
je vlastní formát data společnosti Apple a sleduje počet sekund a milisekund od 1. ledna 2001. Mimo platformy společnosti Apple není příliš užitečný. - Formát
.millisecondsSince1970
sleduje počet sekund a milisekund od 1. ledna 1970. To je na internetu docela běžné. - Formát
.secondsSince1970
sleduje počet celých sekund od 1. ledna 1970. Tento formát je na internetu extrémně rozšířený a je druhý po formátu ISO-8601.
Práce s vlastními daty
Pokud váš formát data neodpovídá žádné ze zabudovaných možností, nezoufejte:
Například tento JSON sleduje den, kdy student ukončil studium na univerzitě:
let json = """"""
To používá formát data DD-MM-YYYY, který není jednou z vestavěných možností systému Swift. Naštěstí můžete jako strategii dekódování data poskytnout předkonfigurovanou instanci DateFormatter
, například takto:
let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)
Práce s podivnými daty
Někdy dostanete tak podivná data, že si s nimi neporadí ani DateFormatter
. Například můžete dostat JSON, který ukládá data pomocí počtu dní, které uplynuly od 1. ledna 1970:
let json = """"""
Aby to fungovalo, musíme napsat vlastní dekodér pro datum. Vše ostatní bude stále zpracovávat Codable
– my pouze poskytujeme vlastní uzávěr, který bude zpracovávat část data.
Můžete zde zkusit provést nějakou hacky matematiku, například vynásobit počet dní 86400 (počet sekund v jednom dni) a pak použít metodu addTimeInterval()
z Date
. To však nezohlední letní čas a další problémy s datem, takže lepším řešením je použít DateComponents
a Calendar
takto:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .custom { decoder in // pull out the number of days from Codable let container = try decoder.singleValueContainer() let numberOfDays = try container.decode(Int.self) // create a start date of Jan 1st 1970, then a DateComponents instance for our JSON days let startDate = Date(timeIntervalSince1970: 0) var components = DateComponents() components.day = numberOfDays // create a Calendar and use it to measure the difference between the two let calendar = Calendar(identifier: .gregorian) return calendar.date(byAdding: components, to: startDate) ?? Date()}
Upozornění:
Parsování hierarchických dat jednoduchým způsobem
Každý netriviální JSON bude pravděpodobně obsahovat hierarchická data – jednu kolekci dat vnořenou do druhé. Například:
let json = """"""
Codable
si s tím poradí v pohodě, pokud dokážete jasně popsat vztahy.
Nejjednodušší způsob, jak to udělat, je použít vnořené struktury, například takto:
struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}
Nevýhodou je, že pokud chcete přečíst křestní jméno uživatele, musíte použít user.name.firstName
, ale alespoň je vlastní práce s parsováním triviální – náš stávající kód už funguje!
Parsování hierarchických dat složitějším způsobem
Pokud chcete parsovat hierarchická data do ploché struktury – tj, chcete mít možnost zapsat user.firstName
místo user.name.firstName
– pak musíte provést nějaké parsování sami. Není to však příliš těžké a díky Codable
je to krásně typově bezpečné.
Nejprve vytvořte strukturu, kterou chcete mít na konci:
struct User: Codable { var firstName: String var lastName: String var age: Int}
Druhé musíme definovat kódovací klíče, které popisují, kde se data v hierarchii nacházejí.
Podívejme se znovu na JSON:
let json = """"""
Jak vidíte, v kořeni je klíč s názvem „jméno“ a další s názvem „věk“, takže je musíme přidat jako naše kořenové kódovací klíče. Toto vložte dovnitř vaší struktury:
enum CodingKeys: String, CodingKey { case name, age}
Uvnitř „name“ byly další dva klíče, „first_name“ a „last_name“, takže vytvoříme kódovací klíče pro tyto dva. Přidejte toto:
enum NameCodingKeys: String, CodingKey { case firstName, lastName}
Teď ta těžší část: musíme napsat vlastní inicializátor a vlastní kódovací metodu pro náš typ. Začněte tím, že do svého struktu přidáte tuto prázdnou metodu:
init(from decoder: Decoder) throws {}
Uvnitř se nejprve musíme pokusit vytáhnout kontejner, který můžeme číst pomocí klíčů našeho výčtu CodingKeys
, například takto:
let container = try decoder.container(keyedBy: CodingKeys.self)
Jakmile to uděláme, můžeme se pokusit číst naši vlastnost age
. To se provádí typově bezpečným způsobem: sdělíte mu typ, který chcete dekódovat (Int.self
pro náš věk), spolu se jménem klíče z výčtu CodingKeys
:
age = try container.decode(Int.self, forKey: .age)
Dále se musíme prokopat o úroveň níže, abychom mohli přečíst naše údaje o jménu. Jak jste viděli dříve, „jméno“ je klíč nejvyšší úrovně uvnitř našeho výčtu CodingKeys
, ale ve skutečnosti je to vnořený kontejner dalších hodnot, které potřebujeme přečíst uvnitř. Musíme tedy tento kontejner vytáhnout:
let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
A nakonec můžeme přečíst dva řetězce pro klíče .firstName
a .lastName
:
firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)
Tím končí vlastní inicializátor, ale ještě nám zbývá napsat jednu metodu: encode(to:)
. To je vlastně opak inicializátoru, který jsme právě napsali, protože jeho úkolem je převést naše vlastnosti podle potřeby zpět na vnořený kontejner.
To znamená vytvořit kontejner založený na našem výčtu CodingKeys
a zapsat do něj age
, pak vytvořit vnořený kontejner založený na našem výčtu NameCodingKeys
a zapsat do něj jak firstName
, tak lastName
:
func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(age, forKey: .age) var name = container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name) try name.encode(firstName, forKey: .firstName) try name.encode(lastName, forKey: .lastName)}
Tím je celý náš kód hotov. Díky tomu nyní můžete číst vlastnost firstName
přímo, což je mnohem příjemnější!
SPONSORED Vytváření a udržování infrastruktury pro předplatné v aplikacích je náročné. Naštěstí existuje lepší způsob. S RevenueCatem můžete implementovat předplatné pro svou aplikaci v řádu hodin, ne měsíců, takže se můžete vrátit k tvorbě aplikace.
Vyzkoušejte zdarma
Sponzorujte Hacking with Swift a oslovte největší komunitu uživatelů Swiftu na světě!
Leave a Reply