Working with Codable and JSON in Swift

Scritto da Reinder de Vries il 20 gennaio 2021 in App Development, iOS, Swift

Lavorare con Codable e JSON in Swift

È possibile utilizzare Codablein Swift per codificare e decodificare formati di dati personalizzati, come JSON, in oggetti Swift nativi. È incredibilmente facile mappare oggetti Swift su dati JSON, e viceversa, semplicemente adottando il protocollo Codable.

Come sviluppatore iOS pragmatico, vi imbatterete in JSON prima o poi. Ogni servizio web, da Facebook a Foursquare, usa il formato JSON per far entrare e uscire i dati dalla vostra app. Come puoi codificare e decodificare quei dati JSON in oggetti Swift in modo efficace?

In questo tutorial imparerai come lavorare con gli oggetti JSON in Swift, utilizzando il protocollo Codable. Quello che imparerete, può essere facilmente applicato anche ad altri formati di dati. Ci addentreremo nella codifica e decodifica con JSONEncoder e JSONDecoder, e ti mostrerò come mediare tra i dati JSON e Swift.

Pronto? Andiamo.

  1. Inizio: Codifica e decodifica
  2. Il protocollo Codable spiegato
  3. Lavorare con Codable
  4. Decodifica JSON in oggetti Swift con Codable
  5. Codifica di oggetti Swift come JSON con Codable
  6. Lavorare con array annidati e Codable
  7. Altre letture

Inizio: Codifica e decodifica

Quale problema risolve effettivamente il protocollo Codable in Swift? Cominciamo con un esempio.

Immaginate di costruire un’app di ricette. L’app mostra varie ricette in una lista, includendo gli ingredienti, le istruzioni e le informazioni di base sul cibo.

Prendete i dati per l’app da un webservice, e la loro API basata sul cloud. Questa API utilizza il formato dati JSON. Ogni volta che richiedi una ricetta dal webservice, ricevi indietro i dati JSON.

Ecco un esempio di dati JSON per una ricetta:

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

Guarda la struttura dei dati JSON.

Gli oggetti JSON sono avvolti da parentesi graffe { e }, e gli array sono avvolti da parentesi quadre . I nomi delle proprietà sono stringhe, avvolte tra virgolette ". I valori in JSON possono essere stringhe, numeri (senza virgolette), array o altri oggetti. Puoi anche annidare i dati, cioè array in array, oggetti in array, ecc., per creare una complessa gerarchia di dati.

JSON è un formato dati basato sul testo che molti webservices usano, incluse le API di Twitter, Facebook, Foursquare, e così via. Se state costruendo applicazioni che usano risorse basate sul web, vi imbatterete in JSON.

Il formato JSON è superiore a XML, un’alternativa comune, perché è efficiente, facilmente analizzabile e leggibile dagli umani. JSON è un formato concordato per webservices, API e applicazioni. È usato in tutto il web, le applicazioni e i servizi online, perché il formato è semplice e flessibile.

E JSON ha una capacità superba: si può codificare qualsiasi formato di dati in JSON, e decodificare JSON in qualsiasi formato di dati. Questo processo di codifica e decodifica è ciò che rende JSON così potente.

Puoi prendere i tuoi valori Swift String, Int, Double, URL, Date, Data, Array e Dictionary, e codificarli come JSON. Poi li inviate al webservice, che decodifica i valori in un formato nativo che capisce. Allo stesso modo, il webservice invia dati codificati come JSON alla tua app, e tu decodifichi i dati in tipi nativi come String, Double e Array.

Quando la tua app riceve il JSON (vedi sopra), può essere decodificato in una struct Swift, come questa:

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

In Swift, il protocollo Codable è usato per passare da un oggetto dati JSON a una classe o struct Swift effettiva. Questo è chiamato decodifica, perché i dati JSON sono decodificati in un formato che Swift capisce. Funziona anche nell’altro modo: codificare oggetti Swift come JSON.

Il protocollo Codable in Swift è in realtà un alias dei protocolli Decodable e Encodable. Poiché spesso si usano la codifica e la decodifica insieme, si usa il protocollo Codable per ottenere entrambi i protocolli in un colpo solo.

Il fulcro del flusso di lavoro di codifica/decodifica è il protocollo Codable di Swift. Scopriamo come funziona!

Non riuscite a distinguere tra “codifica” e “decodifica”? Pensate a questo: Stiamo convertendo i dati da e in “codice”, come una macchina Enigma o un cifrario segreto. Codificare significa convertire i dati in codice; en-coding, o “in/entro il codice”. Decodifica significa convertire il codice in dati; de-codifica, o “da/a codice”.

Fatti assumere come sviluppatore iOS

Impara a costruire app iOS 14 con Swift 5

Iscriviti al mio corso di sviluppo iOS, e impara come iniziare la tua carriera come sviluppatore iOS professionista.

Il protocollo Codable spiegato

Utilizzare JSON prima di Swift 4 era un po’ una seccatura. Dovevi serializzare tu stesso il JSON con JSONSerialization, e poi fare il type cast di ogni proprietà del JSON al giusto tipo Swift.

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

Lo snippet qui sopra prende solo un valore yield dal JSON, e lo lancia a Int. È estremamente prolisso, ed è difficile rispondere correttamente a potenziali errori e discrepanze di tipo. Anche se funziona, non è l’ideale.

Librerie come SwiftyJSON rendono il lavoro con JSON molto più facile, ma avete ancora bisogno di mappare i dati JSON ai loro rispettivi oggetti e proprietà Swift.

Con Swift 4, e successivi, potete invece usare il protocollo Codable. La vostra struttura o classe Swift deve semplicemente adottare il protocollo Codable, e avrete la codifica e la decodifica JSON fuori dalla scatola, gratis.

Il protocollo Codable è una composizione di due protocolli, Decodable e Encodable. Entrambi i protocolli sono piuttosto minimali; definiscono solo le funzioni init(from: Decoder) e encode(to: Encoder), rispettivamente. In altre parole, queste funzioni significano che per essere “decodificabile” o “codificabile”, un tipo dovrà “decodificare da qualcosa” e “codificare in qualcosa”.

La vera magia di Codable avviene con i protocolli Decoder e Encoder. Questi protocolli sono adottati dai componenti che codificano/decodificano vari formati di dati, come JSON. In Swift, abbiamo alcuni -codificatori:

  • PropertyListEncoder e PropertyListDecoder per le liste di proprietà .plist
  • JSONEncoder e JSONDecoder per JSON – siamo noi!
  • NSKeyedArchiver può lavorare con Codable, via PropertyListEncoder, Data e NSCoding

Le classi JSONDecoder e JSONEncoder usano i protocolli Decoder e Encoder per fornire la funzionalità per decodificare/codificare JSON. Questo significa anche che puoi scrivere il tuo codificatore/decodificatore personalizzato per Codable, a condizione che tu adotti i protocolli Decoder e Encoder!

Hai bisogno di un ripasso sui protocolli in Swift? Leggi Protocolli in Swift spiegati per aggiornarti.

Lavorare con Codable

Diamo un’occhiata ad un esempio. Stiamo per mappare alcuni dati JSON in una struct Swift. Stiamo essenzialmente decodificando il JSON in un vero oggetto Swift.

Prima di tutto, creiamo una struct chiamata User. In questo modo:

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

La struct User ha tre semplici proprietà di tipo String, ed è conforme al protocollo Codable.

Poi, scriviamo un po’ di JSON. Questo è il JSON con cui lavoreremo:

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

I dati JSON tipicamente entrano nella vostra app come risposta di una richiesta di webservice, o attraverso un file .json, ma per questo esempio mettiamo semplicemente il JSON in un String. Come questo:

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

Nota: Il codice qui sopra usa le triple virgolette """ per creare stringhe multilinea. Bello!

Quello che faremo dopo è decodificare il JSON e trasformarlo in un oggetto User. In questo modo:

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

Ecco cosa succede:

  • Prima si trasforma jsonString in un oggetto Data chiamando la funzione data(using:) sulla stringa. Questo è un passo intermedio necessario.
  • Poi, si crea un oggetto JSONDecoder e si chiama immediatamente la funzione decode(_:from:) su di esso. Questo trasforma il jsonData in un oggetto di tipo User, decodificando il JSON. Il tipo User.self è fornito come parametro.
  • Finalmente, si stampa il cognome dell’utente con print(user.last_name). Quel valore user ha User come tipo, quindi è un vero oggetto Swift!

Facile, vero? Hai essenzialmente “mappato” l’oggetto JSON in una struct Swift, e decodificato il formato JSON in un oggetto nativo con cui Swift può lavorare.

Nel codice sopra, stiamo ignorando qualsiasi errore lanciato da decode(_:from:) con try!. Nel vostro codice, dovrete gestire gli errori con un blocco do-try-catch. Un errore che può essere lanciato, per esempio, deriva dal fornire JSON non valido.

Il User.self è un metatype, un riferimento al tipo User stesso. Stiamo dicendo al decoder che vogliamo decodificare i dati con la struct User, fornendogli un riferimento a quel tipo.

Un errore comune quando si definisce il tipo Codable, come la struct User, è la mancata corrispondenza di chiavi e proprietà. Se hai aggiunto una proprietà alla tua struct, che non è presente nel JSON o ha un tipo diverso, avrai un errore quando decodifichi il JSON. L’opposto, cioè una proprietà che non esiste nella tua struct ma esiste nel JSON, non è un problema. È più facile fare il debug di questa proprietà alla volta, cioè continuare ad aggiungere proprietà fino a quando il JSON non viene più decodificato senza errori. Puoi anche determinare quali proprietà/chiavi includere o ignorare con l’enum CodingKeys (vedi sotto).

Decodificare JSON in oggetti Swift con Codable

Diamo un’altra occhiata all’esempio della sezione precedente, ed espandiamolo. Ecco il JSON con cui stiamo lavorando:

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

Lo trasformiamo in un oggetto Data. Come questo:

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

Questo è un necessario passo intermedio. Invece di rappresentare il JSON come una stringa, ora memorizziamo il JSON come un oggetto nativo Data. La codifica dei caratteri che stiamo usando per quella stringa è UTF8.

Se guardate attentamente, vedrete che il codice di cui sopra usa l’unwrapping forzato per lavorare con il valore di ritorno opzionale da data(using:). Scartiamo l’opzionale in modo più elegante!

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

Con il codice precedente possiamo rispondere agli errori quando data(using:) restituisce nil. Si potrebbe per esempio mostrare un messaggio di errore, o lasciare che il task fallisca silenziosamente e salvare le informazioni diagnostiche in un log.

Prossimo, creiamo un nuovo oggetto JSONDecoder. In questo modo:

let decoder = JSONDecoder()

Utilizziamo poi quel decoder per decodificare i dati JSON. In questo modo:

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

Tuttavia, la funzione decode(_:from:) può lanciare errori. Il codice di cui sopra va in crash ogni volta che ciò accade. Di nuovo, vogliamo rispondere a qualsiasi errore che potrebbe accadere. In questo modo:

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

E l’intero blocco di codice ora assomiglia al seguente. Vedete come è diverso?

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

Non tacete mai gli errori. Catturate l’errore e rispondete ad esso, sia con l’UI/UX, riprovando l’operazione, o loggando, andando in crash e correggendolo.

Lavorare con CodingKeys

Cosa succede se le nostre proprietà JSON, come first_name e/o firstName, sono diverse dalle proprietà delle struct Swift? È qui che entra in gioco CodingKeys.

Ogni classe o struct conforme a Codable può dichiarare una speciale enumerazione annidata chiamata CodingKeys. La si usa per definire le proprietà che devono essere codificate e decodificate, compresi i loro nomi.

Diamo un’occhiata a un esempio. Nella User struct qui sotto, abbiamo cambiato i nomi delle proprietà da snake_case a camelCase. Per esempio, la chiave first_name si chiama ora 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 }}

Quando usate la struct di cui sopra con gli esempi delle sezioni precedenti, vedrete che potete usare un oggetto User con i nuovi nomi delle proprietà. Come questo:

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

L’enumerazione CodingKeys mappa i suoi casi alle proprietà, e usa i valori delle stringhe per selezionare i giusti nomi delle proprietà nei dati JSON. Il caso nell’enum è il nome della proprietà che vuoi usare nella struct, e il suo valore è il nome della chiave nei dati JSON.

Codificare gli oggetti Swift come JSON con Codable

Finora ci siamo concentrati sulla decodifica dei dati JSON in oggetti Swift. Che ne dite di andare nella direzione opposta? Possiamo anche codificare oggetti come JSON? Sì, perché no!

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)

E l’output è:

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

Cosa succede qui?

  • Prima, crei un oggetto User e assegni alcuni valori alle sue proprietà
  • Poi, usi encode(_:) per codificare l’oggetto user in un oggetto JSON Data
  • Finalmente, si converte l’oggetto Data in String e lo si stampa

Se si guarda attentamente, si vedrà che la codifica segue gli stessi passi della decodifica, ma al contrario. Andando dall’oggetto Swift, attraverso il decodificatore, si ottiene una stringa JSON.

Proprio come prima, possiamo espandere l’esempio per gestire gli errori? Sì! In questo modo:

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

Nell’esempio precedente, stiamo anche usando la proprietà outputFormatting del codificatore per “stampare bene” i dati JSON. Questo aggiunge spazi, tabulazioni e newlines per rendere la stringa JSON più facile da leggere. Come questo:

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

Fantastico!

Vale la pena notare che i dizionari in Swift, e in JSON, non hanno un ordine di ordinamento fisso. Questo è il motivo per cui vedrete ordini di ordinamento diversi per le chiavi country, first_name, ecc. nel JSON, se eseguite il codice di cui sopra alcune volte. La maggior parte dei servizi web che emettono JSON impongono un ordine di ordinamento fisso attraverso uno schema, il che rende sicuramente più facile leggere e navigare il JSON.

Lavorare con array annidati e codificabili

Finora abbiamo lavorato con semplici oggetti JSON – solo una classe con alcune proprietà. Ma cosa succede se i dati con cui stai lavorando sono più complessi?

Per esempio, i dati JSON che potresti ricevere da un servizio web di Twitter o Facebook sono spesso annidati. Cioè, i dati che volete raggiungere sono sepolti in 2-3 livelli di array e dizionari JSON. E adesso?

Prima guardiamo un semplice esempio. Ecco la struttura Swift con cui lavoreremo:

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

Poi, ecco i dati JSON che vogliamo decodificare:

let jsonString = """"""

Se guardi bene, vedi che i dati di cui abbiamo bisogno sono memorizzati come un array. Il JSON non è un semplice oggetto, ma è un array con 3 oggetti User. Sappiamo che è un array di oggetti, a causa delle parentesi quadre (array) e le parentesi graffe { } (oggetto). Gli elementi multipli sono sempre separati da virgole.

Come possiamo decodificare questi oggetti User? Ecco come:

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

Lo snippet di codice qui sopra è estremamente simile a quello che avete visto prima, ma c’è una differenza importante. Vedete il tipo che stiamo fornendo alla funzione decode(_:from:)? Il tipo è (con .self), o “array di oggetti Utente”. Invece di lavorare con un solo oggetto User, vogliamo decodificarne un mucchio, e questo è designato con il tipo array.

Prossimamente, parleremo di come potete lavorare con tipi annidati più complessi. Consideriamo, per esempio, che l’array di oggetti User sia annidato dentro un dizionario JSON. In questo modo:

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

Nello snippet JSON qui sopra, l’elemento di primo livello è un dizionario (o “oggetto”). Ha solo una coppia chiave-valore: un array users. I dati che vogliamo sono dentro quell’array. Come ci arriviamo?

È importante che il nostro approccio non sia troppo complicato. È facile pensare che avrete bisogno di un parsing JSON avanzato, ma come si è scoperto, possiamo anche annidare le strutture Swift. Descriveremo l’intero JSON come una struct, con la struct User al suo interno.

Ecco, guardate qui:

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

Ecco cosa succede:

  • Il dizionario JSON di primo livello corrisponde al tipo Response. Proprio come prima, questo tipo è conforme a Codable, per supportare la codifica e la decodifica JSON.
  • All’interno di quella struct abbiamo definito un’altra struct chiamata User. Questo è esattamente lo stesso tipo che abbiamo usato prima. Questa struct è “annidata”.
  • La struct Response ha una proprietà chiamata users di tipo , o array di oggetti User.

La cosa bella è che semanticamente, entrambe le strutture dati JSON e Swift sono esattamente le stesse. Usano solo una sintassi diversa. Abbiamo definito un array annidato all’interno del dizionario di primo livello, proprio come la Response struct ha una User struct annidata e users proprietà al suo interno.

Farlo funzionare è un gioco da ragazzi ora. Controllate questo:

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

Vedete come stiamo usando il tipo Response per decodificare il JSON, e poi fare un loop sulla proprietà response.users? Controllate questo con la struttura Response e il JSON. Stiamo prendendo la coppia chiave-valore users nel dizionario di primo livello, e mappiamo gli oggetti all’interno in oggetti User. Bello!

L’uso di tipi annidati è un grande approccio generale alle strutture dati JSON complesse. Quando ti imbatti in un pezzo di JSON che non puoi facilmente rappresentare in un tipo tangibile, come User o Tweet, prova ad espandere il tipo a qualcosa come Response o UserCollection. Invece di andare più in profondità, andate più in là, e incorporate il JSON completo. Usate i tipi annidati per arrivare ai dati che volete. Vale anche la pena notare qui che non è necessario aggiungere la struct User nella struct Response – può andare anche al di fuori di essa.

Fatti assumere come sviluppatore iOS

Impara a costruire app iOS 14 con Swift 5

Iscriviti al mio corso di sviluppo iOS, e impara come iniziare la tua carriera come sviluppatore iOS professionista.

Ulteriori letture

E questo è tutto! Ora lo sai:

  • Come usare Codable per creare oggetti che possono essere codificati e decodificati
  • Come usare JSONDecoder e JSONEncoder per codificare e decodificare oggetti JSON e le loro controparti Swift
  • A cosa servono la codifica e la decodifica, e perché è importante nello sviluppo quotidiano di iOS
  • Come lavorare con dati JSON più complessi e come usare i tipi annidati

Vuoi saperne di più? Guarda queste risorse:

  • Array, dizionari e strutture
  • Lavorare con JSON in Swift con SwiftyJSON
  • Introduzione alla programmazione orientata agli oggetti in Swift
  • Come: Trovare un elemento in un array in Swift
  • Come usare la documentazione per sviluppatori Apple per divertimento e profitto
  • Come trovare stringhe con espressioni regolari in Swift
  • Perché L’architettura delle app è importante
  • Spiegazione delle stringhe in Swift
  • Lavorare con i file su iOS con Swift
  • Memorizzare i dati con NSCoding e NSKeyedArchiver

Lascia un commento