Codable cheat sheet
Codable
oli yksi Swift 4.0:n kulmakiviominaisuuksista, joka toi mukanaan uskomattoman sujuvan muuntamisen Swift-tietotyyppien ja JSONin välillä. Sitten se parani entisestään Swift 4.1:ssä uusien toimintojen lisäämisen ansiosta, ja odotan vielä suurempia asioita tulevan tulevaisuudessa.
Tässä artikkelissa haluan tarjota nopeita koodinäytteitä, jotka auttavat sinua vastaamaan yleisimpiin kysymyksiin ja ratkaisemaan yleisimpiä ongelmia, kaikki Codable
:n avulla.
SPONSOROITU Sovelluksen sisäisen tilausinfrastruktuurin rakentaminen ja ylläpito on raskasta. Onneksi on olemassa parempi tapa. RevenueCatilla voit toteuttaa tilaukset sovellukseesi tunneissa, ei kuukausissa, joten voit palata takaisin sovelluksesi rakentamiseen.
Kokeile ilmaiseksi
Sponsoroi Hacking with Swift ja tavoita maailman suurin Swift-yhteisö!
JSONin koodaaminen ja purkaminen
Aloitetaan perusasioista: muunnetaan hieman JSONia Swift-rakenteiksi.
Aluksi tässä on hieman JSONia, jonka kanssa työskennellä:
let json = """"""let data = Data(json.utf8)
Viimeinen rivi muuntaa sen Data
-olioksi, koska Codable
-dekooderit työskentelevät sen kanssa.
Seuraavaksi meidän on määriteltävä Swift-rakenne, joka pitää sisällään valmiit tietomme:
struct User: Codable { var name: String var age: Int}
Nyt voimme mennä eteenpäin ja suorittaa dekoodauksen:
let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}
Tällöin tulostuu ”Paul”, joka on JSON:in ensimmäisen käyttäjän nimi.
Muunnetaan tapaus
Yleinen ongelma JSON:in kohdalla on se, että se käyttää erilaista muotoilua avaintensa nimille, kuin mitä haluaisimme käyttöömme Swiftissä. Saatat esimerkiksi saada JSONissasi ”etunimi” ja sinun täytyy muuntaa se firstName
-ominaisuudeksi.
Nyt yksi ilmeinen ratkaisu tähän on vain muuttaa joko JSON- tai Swift-tyypit niin, että ne käyttävät samaa nimeämiskäytäntöä, mutta emme aio tehdä sitä tässä. Sen sijaan oletan, että sinulla on tämänkaltaista koodia:
let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}
Voidaksemme saada tämän toimimaan meidän on muutettava vain yhtä ominaisuutta JSON-dekooderissamme:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase
Se neuvoo Swiftiä kartoittamaan käärmeen tapaiset nimet (names_written_like_this) kamelin tapaisiin nimiin (namesWrittenLikeThis).
Erilaisten avainten nimien kartoittaminen
Jos sinulla on JSON-avaimia, jotka ovat täysin erilaisia kuin Swift-ominaisuutesi, voit kartoittaa ne käyttämällä CodingKeys
enumia.
Katso tätä JSON:
let json = """"""
Nämä avainten nimet eivät ole hyviä, ja oikeastaan haluaisimme muuntaa tuon datan seuraavanlaiseksi rakenteeksi:
struct User: Codable { var firstName: String var lastName: String var age: Int}
Taataksemme sen, meidän täytyy julistaa CodingKeys
enum: kartoitus, jota Codable
voi käyttää muuntamaan JSON-nimet rakenteemme ominaisuuksiksi. Tämä on tavallinen enum, joka käyttää merkkijonoja raa’ana arvona, jotta voimme määrittää sekä ominaisuutemme nimen (enumin tapaus) että JSON-nimen (enumin arvo) samanaikaisesti. Sen on myös oltava CodingKey
-protokollan mukainen, mikä saa tämän toimimaan Codable
-protokollan kanssa.
Lisää siis tämä enum structiin:
enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}
Se pystyy nyt purkamaan JSONin suunnitellusti.
Huomaa: Enumin nimi on CodingKeys
ja protokollan nimi on CodingKey
.
Työskentely ISO-8601-päivämäärien kanssa
Internetissä on monia tapoja työskennellä päivämäärien kanssa, mutta ISO-8601 on yleisin. Se koodaa täydellisen päivämäärän muodossa VVVV-KK-PP, sitten kirjaimen ”T” aikatiedon alkamisen merkiksi, sitten kellonajan muodossa HH:MM:SS ja lopuksi aikavyöhykkeen. Aikavyöhyke ”Z”, joka on lyhenne sanoista ”Zulu time”, tarkoittaa yleisesti UTC:tä.
Codable
pystyy käsittelemään ISO-8601:ää sisäänrakennetun päivämäärämuuntimen avulla. Annettuna tämä JSON:
let json = """"""
Voidaan siis purkaa se näin:
struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601
Tämä mahdollistaa ISO-8601-päivämäärän jäsennyksen, joka muuntaa 1999-04-03T17:30:31Z:n Date
-instanssiksi, samalla kun se käsittelee myös käärmeen tapaus-kirjaimen muuntamisen kamelin tapaus-kirjaimeksi.
Työskentely muiden yleisten päivämäärien parissa
Swiftissä on sisäänrakennettu tuki kolmelle muullekin tärkeälle päivämäärän formaatille. Käytät niitä aivan kuten edellä esitettyjä ISO-8601-päivämääriä, joten puhun niistä vain lyhyesti:
- Muoto
.deferredToDate
on Applen oma päivämäärämuoto, ja se seuraa sekuntien ja millisekuntien lukumäärää tammikuun 1. päivästä 2001 lähtien. Tästä ei ole oikeastaan hyötyä Applen alustojen ulkopuolella. - Muodossa
.millisecondsSince1970
seurataan sekuntien ja millisekuntien määrää 1. tammikuuta 1970 lähtien. Tämä on melko yleistä verkossa. - Muodossa
.secondsSince1970
seurataan kokonaisten sekuntien määrää 1. tammikuuta 1970 lähtien. Tämä on verkossa erittäin yleinen, ja se on toiseksi yleisin ISO-8601:n jälkeen.
Työskentely mukautetuilla päivämäärillä
Jos päivämäärämuotosi ei vastaa jotakin sisäänrakennetuista vaihtoehdoista, älä masennu: Codable voi jäsentää mukautettuja päivämääriä luomasi päivämäärämuotoilijan perusteella.
Esimerkiksi tämä JSON jäljittää päivän, jolloin opiskelija valmistui yliopistosta:
let json = """"""
Tässä käytetään päivämäärämuotoa pp.kk.vvvvv-vvvv, joka ei kuulu Swiftin sisäänrakennettuihin vaihtoehtoihin. Onneksi voit tarjota valmiiksi konfiguroidun DateFormatter
-instanssin päivämäärän dekoodausstrategiaksi, kuten tässä:
let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)
Työskentely outojen päivämäärien kanssa
Joskus saat niin outoja päivämääriä, että edes DateFormatter
ei pysty käsittelemään niitä. Saatat esimerkiksi saada JSON:ia, joka tallentaa päivämäärät käyttäen 1. tammikuuta 1970 jälkeen kuluneiden päivien lukumäärää:
let json = """"""
Taataksemme sen toimivaksi meidän on kirjoitettava päivämäärälle oma dekooderi. Kaikki muu hoidetaan edelleen Codable
:llä – me vain tarjoamme mukautetun sulkemisen, joka käsittelee päivämäärät-osion.
Voisit kokeilla tehdä tässä hieman hakkeroitua matematiikkaa, kuten kertoa päivien lukumäärän 86400:lla (sekuntien määrä päivässä) ja käyttää sitten Date
:n addTimeInterval()
-menetelmää. Tämä ei kuitenkaan ota huomioon kesäaikaa ja muita päivämääräongelmia, joten parempi ratkaisu on käyttää DateComponents
ja Calendar
näin:
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()}
Varoitus:
Hierarkkisen datan jäsentäminen helpolla tavalla
Jokainen ei-triviaali JSON sisältää todennäköisesti hierarkkista dataa – yksi kokoelma dataa sisäkkäin toisen sisällä. Esimerkiksi:
let json = """"""
Codable
pystyy käsittelemään tätä hienosti, kunhan pystyt kuvaamaan suhteet selkeästi.
Havaitsen helpoimman tavan tehdä tämän käyttämällä sisäkkäisiä structteja, kuten tämä:
struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}
Haitaksi jää se, että jos haluat lukea käyttäjän etunimen, sinun täytyy käyttää user.name.firstName
, mutta ainakin varsinainen jäsentelytyö on triviaalia – olemassa oleva koodimme toimii jo nyt!
Hierarkkisen datan jäsentäminen vaikealla tavalla
Jos haluat jäsentää hierarkkisen datan litteäksi rakenteeksi – ts, haluat pystyä kirjoittamaan user.firstName
eikä user.name.firstName
– silloin sinun täytyy tehdä jäsentelyä itse. Tämä ei kuitenkaan ole kovin vaikeaa, ja Codable
tekee siitä kauniin tyyppiturvallisen.
Luo ensin struct, jonka haluat lopputulokseksi:
struct User: Codable { var firstName: String var lastName: String var age: Int}
Toiseksi meidän on määriteltävä koodausavaimet, jotka kuvaavat, mistä kohtaa hierarkiaa data löytyy.
Katsotaan JSONia uudelleen:
let json = """"""
Kuten näet, juuressa on avain nimeltä ”name” ja toinen nimeltä ”age”, joten meidän on lisättävä ne juuren koodausavaimiksi. Laita tämä rakenteesi sisään:
enum CodingKeys: String, CodingKey { case name, age}
Sisällä ”nimi” oli kaksi muuta avainta, ”etunimi” ja ”sukunimi”, joten luomme koodausavaimet näille kahdelle. Lisää tämä:
enum NameCodingKeys: String, CodingKey { case firstName, lastName}
Nyt tulee vaikein osa: meidän täytyy kirjoittaa mukautettu initialisoija ja mukautettu koodausmetodi tyypillemme. Aloita lisäämällä tämä tyhjä metodi rakenteeseesi:
init(from decoder: Decoder) throws {}
Sen sisällä meidän on ensimmäiseksi yritettävä vetää ulos säiliö, jota voimme lukea käyttämällä CodingKeys
– enumimme avaimia, näin:
let container = try decoder.container(keyedBy: CodingKeys.self)
Kun se on tehty, voimme yrittää lukea age
-ominaisuuttamme. Tämä tapahtuu tyyppiturvallisella tavalla: kerrot sille tyypin, jonka haluat purkaa (Int.self
meidän ikäisemme kohdalla), sekä avaimen nimen CodingKeys
enumista:
age = try container.decode(Int.self, forKey: .age)
Seuraavaksi meidän on kaivettava yksi taso alaspäin lukeaksemme nimitietomme. Kuten aiemmin huomasit, ”nimi” on ylimmän tason avain CodingKeys
-enumissamme, mutta se on itse asiassa muiden arvojen sisäkkäinen säiliö, jonka sisältä meidän on luettava. Meidän on siis vedettävä tuo säiliö ulos:
let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
Ja lopuksi voimme lukea kaksi merkkijonoa avaimille .firstName
ja .lastName
:
firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)
Tämä päättää mukautetun alustajan, mutta meillä on vielä yksi metodi kirjoitettavana: encode(to:)
. Tämä on käytännössä päinvastainen kuin äsken kirjoittamamme initiaattori, koska sen tehtävänä on muuntaa ominaisuutemme takaisin sisäkkäisiksi säiliöiksi soveltuvin osin.
Tämä tarkoittaa, että luomme säiliön, joka perustuu CodingKeys
-luokitukseemme, ja kirjoitamme sinne age
, sitten luomme sisäkkäisen säiliön, joka perustuu NameCodingKeys
-luokitukseemme, ja kirjoitamme sinne sekä firstName
:n että 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)}
Näillä koodimme on valmis. Kun tämä on paikallaan, voit nyt lukea firstName
-ominaisuutta suoraan, mikä on paljon mukavampaa!
SPONSOROITU Sovelluksen sisäisen tilausinfrastruktuurin rakentaminen ja ylläpito on vaikeaa. Onneksi on olemassa parempi tapa. RevenueCatilla voit toteuttaa tilaukset sovellukseesi tunneissa, ei kuukausissa, joten voit palata takaisin sovelluksesi rakentamiseen.
Kokeile ilmaiseksi
Sponsoroi Hacking with Swift ja tavoita maailman suurin Swift-yhteisö!
Leave a Reply