Lucrând cu Codable și JSON în Swift

Scris de Reinder de Vries pe 20 ianuarie 2021 în Dezvoltare aplicații, iOS, Swift

Working with Codable and JSON in Swift

Puteți utiliza Codable în Swift pentru a codifica și decodifica formate de date personalizate, cum ar fi JSON, în obiecte native Swift. Este incredibil de ușor să mapezi obiecte Swift în date JSON și viceversa, prin simpla adoptare a protocolului Codable.

Ca dezvoltator iOS pragmatic, veți întâlni JSON mai devreme sau mai târziu. Fiecare serviciu web, de la Facebook la Foursquare, utilizează formatul JSON pentru a introduce și a scoate date din aplicația dvs. Cum puteți codifica și decodifica eficient acele date JSON în obiecte Swift?

În acest tutorial veți învăța cum să lucrați cu obiecte JSON în Swift, folosind protocolul Codable. Ceea ce veți învăța, poate fi aplicat cu ușurință și la alte formate de date. Vom intra în codificare și decodificare cu JSONEncoder și JSONDecoder, și vă voi arăta cum să mediați între datele JSON și Swift.

Pregătiți? Să mergem.

  1. Începeți: Codificare și decodificare
  2. Explicarea protocolului Codable
  3. Lucrul cu Codable
  4. Decodarea JSON în obiecte Swift cu Codable
  5. Codificarea obiectelor Swift ca JSON cu Codable
  6. Lucrul cu array-uri imbricate și Codable
  7. Lecturi suplimentare

Începeți: Codificare și decodificare

Ce problemă rezolvă de fapt protocolul Codable din Swift? Să începem cu un exemplu.

Imaginați-vă că construiți o aplicație de rețete. Aplicația afișează diverse rețete într-o listă, inclusiv ingredientele, instrucțiunile și informațiile de bază despre alimente.

Obțineți datele pentru aplicație de la un serviciu web și API-ul lor bazat pe cloud. Această API utilizează formatul de date JSON. De fiecare dată când solicitați o rețetă de la serviciul web, primiți înapoi date JSON.

Iată un exemplu de date JSON pentru o rețetă:

{ "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!"}

Aruncați o privire la structura datelor JSON.

Obiectele JSON sunt înfășurate în paranteze zbârcite { și }, iar array-urile sunt înfășurate în paranteze pătrate . Numele proprietăților sunt șiruri de caractere, înfășurate între ghilimele ". Valorile în JSON pot fi șiruri de caractere, numere (fără ghilimele), matrici sau alte obiecte. De asemenea, puteți anina date, adică array-uri în array-uri, obiecte în array-uri etc., pentru a crea o ierarhie complexă de date.

JSON este un format de date bazat pe text pe care îl folosesc multe servicii web, inclusiv API-urile de la Twitter, Facebook, Foursquare și așa mai departe. Dacă construiți aplicații care utilizează resurse bazate pe web, vă veți întâlni cu JSON.

Foratul JSON este superior lui XML, o alternativă comună, deoarece este eficient, ușor de analizat și lizibil de către oameni. JSON este un format agreat pentru servicii web, API-uri și aplicații. Este utilizat pe tot parcursul web-ului, aplicațiilor și serviciilor online, deoarece formatul este simplu și flexibil.

Și JSON are o capacitate superbă: puteți codifica orice format de date în JSON și decodifica JSON înapoi în orice format de date. Acest proces de codificare și decodificare este ceea ce face ca JSON să fie atât de puternic.

Puteți lua valorile Swift String, Int, Double, URL, Date, Data, Array și Dictionary și să le codificați ca JSON. Apoi le trimiteți către serviciul web, care decodifică valorile într-un format nativ pe care îl înțelege. În mod similar, serviciul web trimite aplicației dvs. date codificate ca JSON, iar dvs. decodificați datele în tipuri native, cum ar fi String, Double și Array.

Când aplicația dvs. de rețete primește JSON (a se vedea mai sus), acesta poate fi apoi decodificat într-o structură Swift, ca aceasta:

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

În Swift, protocolul Codable este utilizat pentru a trece de la un obiect de date JSON la o clasă sau structură Swift reală. Acest lucru se numește decodare, deoarece datele JSON sunt decodate într-un format pe care Swift îl înțelege. De asemenea, funcționează și în sens invers: codificarea obiectelor Swift ca JSON.

Protocolul Codable din Swift este de fapt un alias al protocoalelor Decodable și Encodable. Deoarece deseori folosiți codificarea și decodificarea împreună, folosiți protocolul Codable pentru a obține ambele protocoale dintr-o dată.

Piesa centrală a fluxului de lucru de codificare/decodificare este protocolul Codable din Swift. Haideți să aflăm cum funcționează, în continuare!

Nu puteți face diferența între „codificare” și „decodificare”? Gândiți-vă la asta în felul următor: Convertim date din și în „cod”, ca o mașină Enigma sau un cifru secret de criptare. Codificarea înseamnă convertirea datelor în cod; en-coding, sau „în/în cod”. Decodarea înseamnă convertirea codului în date; de-codare, sau „din/în afara codului”.

Înființează-te ca dezvoltator iOS

Învață să construiești aplicații iOS 14 cu Swift 5

Înscrie-te la cursul meu de dezvoltare iOS și învață cum să-ți începi cariera ca dezvoltator iOS profesionist.

Protocolul Codable Explicat

Utilizarea JSON înainte de Swift 4 a fost un pic de o PITA. Trebuia să serializați singur JSON-ul cu JSONSerialization, iar apoi să tipăriți fiecare proprietate din JSON în tipul Swift potrivit.

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

Scurt de mai sus preia doar o valoare yield din JSON și o transformă în Int. Este extrem de verbos și este greu de răspuns corespunzător la eventualele erori și discrepanțe de tip. Chiar dacă funcționează, nu este ideal.

Bibliotecile precum SwiftyJSON facilitează foarte mult lucrul cu JSON, dar tot trebuie să mapezi datele JSON la obiectele și proprietățile lor Swift respective.

Cu Swift 4, și mai târziu, puteți folosi în schimb protocolul Codable. Structura sau clasa dvs. Swift trebuie doar să adopte protocolul Codable și veți obține codificarea și decodificarea JSON din start, gratuit.

Protocolul Codable este o compoziție a două protocoale, Decodable și Encodable. Ambele protocoale sunt destul de minimale; ele definesc doar funcțiile init(from: Decoder) și, respectiv, encode(to: Encoder). Cu alte cuvinte, aceste funcții înseamnă că, pentru ca un tip să fie „decodabil” sau „codabil”, va trebui să „decodeze de la ceva” și să „codifice la ceva”.

Reala magie a Codable se întâmplă cu protocoalele Decoder și Encoder. Aceste protocoale sunt adoptate de componentele care codifică/decodifică diverse formate de date, cum ar fi JSON. În Swift, avem câteva -codificatoare:

  • PropertyListEncoder și PropertyListDecoder pentru listele de proprietăți .plist
  • JSONEncoder și JSONDecoder pentru JSON – asta suntem noi!
  • NSKeyedArchiver poate lucra cu Codable, prin PropertyListEncoder, Data și NSCoding

Classele JSONDecoder și JSONEncoder utilizează protocoalele Decoder și Encoder pentru a furniza funcționalitatea de decodare/codificare JSON. Asta înseamnă, de asemenea, că vă puteți scrie propriul codificator/decodificator personalizat pentru Codable, cu condiția să adoptați protocoalele Decoder și Encoder!

Vreți o reîmprospătare a protocoalelor în Swift? Citiți Protocoale în Swift Explained pentru a vă pune la curent.

Lucrul cu Codable

Să ne uităm la un exemplu. Vom cartografia niște date JSON într-o structură Swift. În esență, decodificăm JSON într-un obiect Swift real.

În primul rând, creăm un struct numit User. Cam așa:

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

Structul User are trei proprietăți simple de tip String și este conform cu protocolul Codable.

Apoi, să scriem un pic de JSON. Acesta este JSON-ul cu care vom lucra:

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

Datele JSON intră de obicei în aplicația dvs. ca răspuns la o cerere de serviciu web sau printr-un fișier .json, dar pentru acest exemplu vom pune pur și simplu JSON-ul într-un String. Astfel:

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

Nota: Codul de mai sus utilizează ghilimelele triple """ pentru a crea șiruri de mai multe linii. Neat!

Ceea ce vom face în continuare, este să decodăm JSON-ul și să-l transformăm într-un obiect User. Cam așa:

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

Iată ce se întâmplă:

  • În primul rând, transformați jsonString într-un obiect Data prin apelarea funcției data(using:) asupra șirului. Acesta este un pas intermediar necesar.
  • Apoi, creați un obiect JSONDecoder și apelați imediat funcția decode(_:from:) asupra acestuia. Aceasta transformă jsonData într-un obiect de tip User, prin decodificarea JSON. Tipul User.self este furnizat ca parametru.
  • În cele din urmă, se tipărește numele de familie al utilizatorului cu print(user.last_name). Această valoare user are ca tip User, deci este un obiect Swift real!

Facil, nu? În esență, ați „cartografiat” obiectul JSON într-o structură Swift și ați decodificat formatul JSON într-un obiect nativ cu care Swift poate lucra.

În codul de mai sus, ignorăm orice erori aruncate de decode(_:from:) cu try!. În propriul cod, va trebui să gestionați erorile cu un bloc do-try-catch. O eroare care poate fi aruncată, de exemplu, provine din furnizarea unui JSON invalid.

User.self este un metatype, o referință la tipul User însuși. Îi spunem decodificatorului că dorim să decodificăm datele cu structura User, furnizându-i o referință la acel tip.

O eroare frecventă atunci când vă definiți tipul Codable, ca și structura User, sunt cheile și proprietățile nepotrivite. Dacă ați adăugat o proprietate la struct, care nu este prezentă în JSON sau are un tip diferit, veți obține o eroare la decodarea JSON. Opusul, adică o proprietate care nu există în structura dumneavoastră, dar există în JSON, nu reprezintă o problemă. Cel mai simplu este să depanați această problemă proprietate pe rând, adică să continuați să adăugați proprietăți până când JSON nu mai decodifică fără erori. De asemenea, puteți determina ce proprietăți/chei să includeți sau să ignorați cu CodingKeys enum (a se vedea mai jos).

Decodarea JSON în obiecte Swift cu Codable

Să ne uităm din nou la exemplul din secțiunea anterioară și să îl extindem. Iată JSON-ul cu care lucrăm:

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

În continuare îl transformăm într-un obiect Data. Astfel:

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

Este un pas intermediar necesar. În loc să reprezentăm JSON-ul ca un șir de caractere, acum stocăm JSON-ul ca un obiect nativ Data. Codificarea caracterelor pe care o folosim pentru acel șir de caractere este UTF8.

Dacă vă uitați cu atenție, veți vedea că codul de mai sus folosește desfacerea forțată pentru a lucra cu valoarea opțională de returnare din data(using:). Haideți să despachetăm mai elegant acea valoare opțională!

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

Cu codul de mai sus putem răspunde la erori atunci când data(using:) returnează nil. Ați putea, de exemplu, să afișați un mesaj de eroare sau să lăsați în tăcere sarcina să eșueze și să salvați informațiile de diagnosticare într-un jurnal.

În continuare, vom crea un nou obiect JSONDecoder. Astfel:

let decoder = JSONDecoder()

Apoi folosim acel decodor pentru a decoda datele JSON. Astfel:

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

Dar, funcția decode(_:from:) poate arunca erori. Codul de mai sus se blochează ori de câte ori se întâmplă acest lucru. Din nou, dorim să răspundem la orice eroare care s-ar putea întâmpla. Astfel:

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

Și întregul bloc de cod arată acum după cum urmează. Vedeți în ce fel este diferit?

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

Nu tăceți niciodată erorile. Prindeți eroarea și răspundeți la ea, fie cu UI/UX, prin reluarea sarcinii, fie prin logare, blocarea și repararea ei.

Lucrul cu CodingKeys

Ce se întâmplă dacă proprietățile noastre JSON, cum ar fi first_name și/sau firstName, sunt diferite de proprietățile structurilor Swift? Aici intervine CodingKeys.

Care clasă sau struct care se conformează la Codable poate declara o enumerare specială imbricata numită CodingKeys. O utilizați pentru a defini proprietățile care ar trebui să fie codificate și decodificate, inclusiv numele lor.

Să ne uităm la un exemplu. În structura User de mai jos, am schimbat numele proprietăților din snake_case în camelCase. De exemplu, cheia first_name se numește acum 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 }}

Când folosiți structura de mai sus cu exemplele din secțiunile anterioare, veți vedea că puteți folosi un obiect User cu noile nume de proprietăți. Astfel:

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

Enumerarea CodingKeys își mapează cazurile în proprietăți și utilizează valorile șirurilor de caractere pentru a selecta numele corecte ale proprietăților în datele JSON. Cazul din enumerație este numele proprietății pe care doriți să o utilizați în struct, iar valoarea sa este numele cheii din datele JSON.

Codificarea obiectelor Swift ca JSON cu Codable

Până acum ne-am concentrat pe decodificarea datelor JSON în obiecte Swift. Cum rămâne cu mersul în sens invers? Putem, de asemenea, să codificăm obiecte ca JSON? Da, de ce nu!

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)

Și ieșirea este:

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

Ce se întâmplă aici?

  • În primul rând, se creează un obiect User și se atribuie niște valori proprietăților sale
  • Apoi, se folosește encode(_:) pentru a codifica obiectul user într-un obiect JSON Data
  • În final, convertiți obiectul Data în String și îl imprimați

Dacă vă uitați cu atenție, veți vedea că codificarea urmează aceiași pași ca și decodificarea, doar că în sens invers. Trecând de la obiectul Swift, prin decodor, rezultând un șir JSON.

La fel ca înainte, putem extinde exemplul pentru a trata erorile? Da! Astfel:

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

În exemplul de mai sus, folosim, de asemenea, proprietatea outputFormatting a codificatorului pentru a „tipări frumos” datele JSON. Aceasta adaugă spații, tabulări și linii noi pentru a face șirul JSON mai ușor de citit. Astfel:

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

Frumos!

Este demn de remarcat faptul că dicționarele în Swift, și în JSON, nu au o ordine de sortare fixă. Acesta este motivul pentru care veți vedea ordine de sortare diferite pentru cheile country, first_name etc. din JSON, dacă executați codul de mai sus de câteva ori. Cele mai multe servicii web care emit JSON vor impune o ordine de sortare fixă printr-o schemă, ceea ce cu siguranță ușurează citirea și navigarea în JSON.

Lucrul cu matrici imbricate și Codable

Până acum am lucrat cu obiecte JSON simple – doar o clasă cu câteva proprietăți. Dar ce se întâmplă dacă datele cu care lucrați sunt mai complexe?

De exemplu, datele JSON pe care le puteți primi înapoi de la un serviciu web Twitter sau Facebook sunt adesea imbricate. Adică, datele la care doriți să ajungeți sunt îngropate în 2-3 niveluri de matrici și dicționare JSON. Ce facem acum!?

Să ne uităm mai întâi la un exemplu simplu. Iată structura Swift cu care vom lucra:

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

După aceea, iată datele JSON pe care vrem să le decodificăm:

let jsonString = """"""

Dacă vă uitați cu atenție, vedeți că datele de care avem nevoie sunt stocate sub forma unui array. JSON-ul nu este un obiect simplu, ci este un array cu 3 obiecte User. Știm că este un array de obiecte, datorită parantezelor pătrate (array) și parantezelor mâzgălite { } (obiect). Elementele multiple sunt întotdeauna separate prin virgule.

Cum putem decoda aceste obiecte User? Iată cum:

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

Scurt de cod de mai sus este extrem de asemănător cu ceea ce ați mai văzut până acum, dar există o diferență importantă. Vedeți tipul pe care îl furnizăm funcției decode(_:from:)? Tipul este (cu .self), sau „array of User objects”. În loc să lucrăm cu un singur obiect User, vrem să decodificăm o grămadă de obiecte, iar acest lucru este desemnat cu tipul array.

În continuare, vom discuta despre cum puteți lucra cu tipuri imbricate mai complexe. Să considerăm, de exemplu, că matricea de obiecte User este imbricata în interiorul unui dicționar JSON. Astfel:

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

În fragmentul JSON de mai sus, elementul de nivel superior este un dicționar (sau „obiect”). Acesta are doar o singură pereche cheie-valoare: o matrice users. Datele pe care le dorim se află în interiorul acestui array. Cum ajungem la ele?

Este important ca abordarea noastră să nu fie prea complicată. Este ușor să credeți că veți avea nevoie de o analiză JSON avansată, dar se pare că, de fapt, putem, de asemenea, anina structurile Swift. Vom descrie întregul JSON ca fiind un struct, cu struct-ul User în interiorul acestuia.

Iată ce se întâmplă:

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

Iată ce se întâmplă:

  • Dicționarul JSON de nivel superior corespunde tipului Response. La fel ca înainte, acest tip este conform cu Codable, pentru a suporta codificarea și decodificarea JSON.
  • În interiorul acestui struct am definit un alt struct numit User. Acesta este exact același tip pe care l-am folosit înainte. Acest struct este „imbricat”.
  • Structul Response are o proprietate numită users de tip , sau array de obiecte User.

Ceea mai tare este că, din punct de vedere semantic, ambele structuri de date JSON și Swift sunt exact la fel. Ele folosesc doar o sintaxă diferită. Am definit o matrice imbricata în interiorul dicționarului de nivel superior, la fel cum structura Response are o structură User și o proprietate users imbricate în interiorul ei.

Făcând-o să funcționeze este floare la ureche acum. Uitați-vă la asta:

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

Vezi cum folosim tipul Response pentru a decoda JSON, iar apoi facem o buclă peste proprietatea response.users? Verificați asta cu structura Response și cu JSON. Preluăm perechea cheie-valoare users din dicționarul de nivel superior și mapăm obiectele din interior în obiecte User. Frumos!

Utilizarea tipurilor imbricate este o abordare excelentă de uz general pentru structurile de date JSON complexe. Când dați peste o bucată de JSON pe care nu o puteți reprezenta cu ușurință într-un tip tangibil, cum ar fi User sau Tweet, încercați să extindeți tipul la ceva de genul Response sau UserCollection. În loc să mergeți mai adânc, mergeți mai larg și încorporați întregul JSON. Utilizați tipuri imbricate pentru a ajunge la datele pe care le doriți. De asemenea, merită menționat aici că nu trebuie să adăugați structura User în structura Response – poate merge și în afara acesteia.

Să te angajezi ca dezvoltator iOS

Învață să construiești aplicații iOS 14 cu Swift 5

Înscrie-te la cursul meu de dezvoltare iOS și învață cum să-ți începi cariera ca dezvoltator iOS profesionist.

Lecturi suplimentare

Și asta e tot! Acum știți:

  • Cum să folosiți Codable pentru a crea obiecte care pot fi codificate și decodificate
  • Cum să folosiți JSONDecoder și JSONEncoder pentru a codifica și decodifica obiecte JSON și omologii lor Swift
  • Cum se utilizează codificarea și decodificarea, și de ce este importantă în dezvoltarea iOS de zi cu zi
  • Cum să lucrați cu date JSON mai complexe și cum să folosiți tipurile imbricate

Vreți să învățați mai mult? Consultați aceste resurse:

  • Array-uri, dicționare și structuri
  • Lucrul cu JSON în Swift cu SwiftyJSON
  • Introducere la programarea orientată pe obiecte în Swift
  • Cum să: Găsirea unui element într-o matrice în Swift
  • Cum să folosiți documentația Apple pentru dezvoltatori pentru distracție și profit
  • Cum să găsiți șiruri de caractere cu expresii regulate în Swift
  • De ce Arhitectura aplicațiilor contează
  • Strings in Swift Explained
  • Working with Files on iOS with Swift
  • Storing Data with NSCoding and NSKeyedArchiver

Lasă un comentariu