Codable cheat sheet
Codable
a fost una dintre caracteristicile de bază ale Swift 4.0, aducând cu ea o conversie incredibil de ușoară între tipurile de date Swift și JSON. Apoi a devenit și mai bună în Swift 4.1 datorită adăugării de noi funcționalități și mă aștept la lucruri și mai mari în viitor.
În acest articol vreau să vă ofer exemple rapide de cod pentru a vă ajuta să răspundeți la întrebări comune și să rezolvați probleme comune, toate folosind Codable
.
SPONSORED Construirea și întreținerea infrastructurii de abonamente în aplicație este dificilă. Din fericire, există o modalitate mai bună. Cu RevenueCat, puteți implementa abonamente pentru aplicația dvs. în câteva ore, nu în câteva luni, astfel încât să vă puteți întoarce la construirea aplicației dvs.
Încercați-l gratuit
Sponsorizați Hacking with Swift și ajungeți la cea mai mare comunitate Swift din lume!
Codificarea și decodificarea JSON
Să începem cu elementele de bază: convertirea unor JSON în structuri Swift.
În primul rând, iată niște JSON cu care să lucrăm:
let json = """"""let data = Data(json.utf8)
Ultima linie îl convertește într-un obiect Data
pentru că cu asta lucrează decodificatoarele Codable
.
În continuare trebuie să definim un struct Swift care va conține datele noastre finalizate:
struct User: Codable { var name: String var age: Int}
Acum putem merge mai departe și să efectuăm decodarea:
let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}
Aceasta va imprima „Paul”, care este numele primului utilizator din JSON.
Convertirea cazului
O problemă comună cu JSON este că va folosi o formatare diferită pentru numele cheilor sale decât cea pe care dorim să o folosim în Swift. De exemplu, s-ar putea să obțineți „first_name” în JSON și să aveți nevoie să convertiți asta într-o proprietate firstName
.
Acum, o soluție evidentă aici este doar să schimbați fie tipul JSON, fie tipul Swift, astfel încât acestea să folosească aceeași convenție de denumire, dar nu vom face asta aici. În schimb, voi presupune că aveți un cod ca acesta:
let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}
Pentru a face ca acest lucru să funcționeze, trebuie să schimbăm doar o singură proprietate în decodorul nostru JSON:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase
Aceasta instruiește Swift să mapeze numele cu majuscule de șarpe (names_written_like_this) în nume cu majuscule de cămilă (namesWrittenLikeThis).
Corectarea unor nume de chei diferite
Dacă aveți chei JSON care sunt complet diferite de proprietățile Swift, le puteți corela folosind un enum CodingKeys
.
Aruncați o privire la acest JSON:
let json = """"""
Numele acestor chei nu sunt grozave și, într-adevăr, am dori să convertim aceste date într-un struct ca acesta:
struct User: Codable { var firstName: String var lastName: String var age: Int}
Pentru ca acest lucru să se întâmple, trebuie să declarăm un enum CodingKeys
: o cartografiere pe care Codable
o poate folosi pentru a converti numele JSON în proprietăți pentru structurile noastre. Acesta este un enum obișnuit care utilizează șiruri de caractere pentru valorile sale brute, astfel încât să putem specifica atât numele proprietății noastre (cazul enum), cât și numele JSON (valoarea enum) în același timp. De asemenea, trebuie să se conformeze protocolului CodingKey
, ceea ce face ca acest lucru să funcționeze cu protocolul Codable
.
Acum, adăugați acest enum la struct:
enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}
Acum va fi capabil să decodifice JSON conform planului.
Nota: Enum-ul se numește CodingKeys
, iar protocolul se numește CodingKey
.
Lucrul cu datele ISO-8601
Există multe moduri de a lucra cu datele pe internet, dar ISO-8601 este cel mai comun. Acesta codifică data completă în formatul AAAA-MM-DD, apoi litera „T” pentru a semnala începutul informațiilor temporale, apoi ora în formatul HH:MM:SS și, în final, un fus orar. Fusul orar „Z”, prescurtare de la „Zulu time”, este utilizat în mod obișnuit pentru a desemna UTC.
Codable
este capabil să gestioneze ISO-8601 cu un convertor de date încorporat. Așadar, având în vedere acest JSON:
let json = """"""
Potem să-l decodificăm astfel:
struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601
Aceasta permite analizarea datei ISO-8601, care convertește din 1999-04-03T17:30:31Z într-o instanță Date
, gestionând în același timp și conversia de la cazul șarpelui la cazul cămilei.
Funcționarea cu alte date comune
Swift vine cu suport încorporat pentru alte trei formate de date importante. Le folosiți la fel cum folosiți datele ISO-8601, așa cum se arată mai sus, așa că voi vorbi doar pe scurt despre ele:
- Formatul
.deferredToDate
este formatul de dată propriu Apple și urmărește numărul de secunde și milisecunde de la 1 ianuarie 2001. Acesta nu este cu adevărat util în afara platformelor Apple. - Foratul
.millisecondsSince1970
urmărește numărul de secunde și milisecunde de la 1 ianuarie 1970. Acest lucru este destul de comun online. - Formatul
.secondsSince1970
urmărește numărul de secunde întregi de la 1 ianuarie 1970. Acesta este extrem de comun online și este al doilea după ISO-8601.
Lucrul cu date personalizate
Dacă formatul de dată nu se potrivește cu una dintre opțiunile încorporate, nu disperați: Codable poate analiza datele personalizate pe baza unui formator de date pe care îl creați.
De exemplu, acest JSON urmărește ziua în care un student a absolvit universitatea:
let json = """"""
Aceasta folosește formatul de date DD-MM-YYYY, care nu este una dintre opțiunile încorporate în Swift. Din fericire, puteți furniza o instanță DateFormatter
preconfigurată ca strategie de decodare a datelor, ca aceasta:
let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)
Lucrând cu date ciudate
Câteodată veți primi date atât de ciudate încât nici măcar DateFormatter
nu le poate gestiona. De exemplu, s-ar putea să primiți JSON care stochează datele folosind numărul de zile care au trecut de la 1 ianuarie 1970:
let json = """"""
Pentru a face acest lucru să funcționeze trebuie să scriem un decodor personalizat pentru dată. Restul va fi în continuare gestionat de Codable
– noi doar furnizăm o închidere personalizată care va procesa partea de date.
Ați putea încerca să faceți niște calcule matematice dificile aici, cum ar fi să înmulțițiți numărul de zile cu 86400 (numărul de secunde dintr-o zi), apoi să folosiți metoda addTimeInterval()
din Date
. Totuși, acest lucru nu va ține cont de ora de vară și de alte probleme legate de date, așa că o soluție mai bună este să folosiți DateComponents
și Calendar
astfel:
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()}
Avertisment: Dacă trebuie să analizați o mulțime de date, nu uitați că această închidere va fi executată pentru fiecare dintre ele – faceți-o rapid!
Analizând date ierarhice în mod simplu
Careva JSON care nu este trivial este probabil să aibă date ierarhice – o colecție de date aninate în alta. De exemplu:
let json = """"""
Codable
este capabil să gestioneze acest lucru foarte bine, atâta timp cât puteți descrie relațiile în mod clar.
Constat că cel mai simplu mod de a face acest lucru este folosirea structurilor imbricate, cum ar fi acesta:
struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}
Inconvenientul este că, dacă doriți să citiți prenumele unui utilizator, trebuie să folosiți user.name.firstName
, dar cel puțin munca efectivă de parsare este trivială – codul nostru existent funcționează deja!
Parisarea datelor ierarhice pe calea cea mai grea
Dacă doriți să analizați datele ierarhice într-o structură plată – de ex, doriți să puteți scrie user.firstName
în loc de user.name.firstName
– atunci trebuie să faceți singuri o analiză. Totuși, acest lucru nu este prea greu, iar Codable
face ca acest lucru să fie minunat de sigur din punct de vedere al tipurilor.
În primul rând, creați structura pe care doriți să o obțineți:
struct User: Codable { var firstName: String var lastName: String var age: Int}
În al doilea rând, trebuie să definim cheile de codare care descriu unde pot fi găsite datele în ierarhie.
Să ne uităm din nou la JSON:
let json = """"""
După cum puteți vedea, la rădăcină există o cheie numită „nume” și o alta numită „vârstă”, așa că trebuie să le adăugăm ca chei de codificare rădăcină. Puneți acest lucru în interiorul structurii dvs.:
enum CodingKeys: String, CodingKey { case name, age}
În interiorul „name” au mai fost două chei, „first_name” și „last_name”, așa că vom crea niște chei de codare pentru acestea două. Adăugați acest lucru:
enum NameCodingKeys: String, CodingKey { case firstName, lastName}
Acum urmează partea dificilă: trebuie să scriem un inițializator personalizat și o metodă de codificare personalizată pentru tipul nostru. Începeți prin a adăuga această metodă goală la structura dvs.:
init(from decoder: Decoder) throws {}
În interiorul acesteia, primul lucru pe care trebuie să îl facem este să încercăm să scoatem un container pe care îl putem citi folosind cheile enumului nostru CodingKeys
, astfel:
let container = try decoder.container(keyedBy: CodingKeys.self)
După ce acest lucru este făcut, putem încerca să citim proprietatea noastră age
. Acest lucru se face într-un mod sigur din punct de vedere al tipului: îi spuneți tipul pe care doriți să îl decodificați (Int.self
pentru vârsta noastră), împreună cu un nume de cheie din enum-ul CodingKeys
:
age = try container.decode(Int.self, forKey: .age)
În continuare trebuie să coborâm un nivel pentru a citi datele despre numele nostru. După cum ați văzut mai devreme, „name” este o cheie de nivel superior în interiorul enumului nostru CodingKeys
, dar este de fapt un container imbricate de alte valori pe care trebuie să le citim în interior. Așadar, trebuie să scoatem acel container:
let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
Și, în cele din urmă, putem citi două șiruri pentru cheile .firstName
și .lastName
:
firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)
Aceasta încheie inițializatorul personalizat, dar mai avem încă o metodă de scris: encode(to:)
. Aceasta este, de fapt, inversul inițializatorului pe care tocmai l-am scris, deoarece sarcina sa este de a converti proprietățile noastre înapoi în containere imbricate, după caz.
Aceasta înseamnă crearea unui container bazat pe enum-ul nostru CodingKeys
și scrierea lui age
acolo, apoi crearea unui container imbricate bazat pe enum-ul nostru NameCodingKeys
și scrierea atât a lui firstName
, cât și a lui lastName
acolo:
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)}
Aceasta încheie tot codul nostru. Cu asta în loc, acum puteți citi direct proprietatea firstName
, ceea ce este mult mai frumos!
SPONSORED Construirea și întreținerea infrastructurii de abonament în aplicație este dificilă. Din fericire, există o modalitate mai bună. Cu RevenueCat, puteți implementa abonamente pentru aplicația dvs. în câteva ore, nu în câteva luni, astfel încât să vă puteți întoarce la construirea aplicației dvs.
Încercați-l gratuit
Sponsorizați Hacking with Swift și ajungeți la cea mai mare comunitate Swift din lume!
.
Leave a Reply