Scritto da Reinder de Vries il 20 gennaio 2021 in App Development, iOS, Swift
È possibile utilizzare Codable
in 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.
- Inizio: Codifica e decodifica
- Il protocollo Codable spiegato
- Lavorare con Codable
- Decodifica JSON in oggetti Swift con Codable
- Codifica di oggetti Swift come JSON con Codable
- Lavorare con array annidati e Codable
- 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
ePropertyListDecoder
per le liste di proprietà .plist -
JSONEncoder
eJSONDecoder
per JSON – siamo noi! - NSKeyedArchiver può lavorare con
Codable
, viaPropertyListEncoder
,Data
eNSCoding
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 oggettoData
chiamando la funzionedata(using:)
sulla stringa. Questo è un passo intermedio necessario. - Poi, si crea un oggetto
JSONDecoder
e si chiama immediatamente la funzionedecode(_:from:)
su di esso. Questo trasforma iljsonData
in un oggetto di tipoUser
, decodificando il JSON. Il tipoUser.self
è fornito come parametro. - Finalmente, si stampa il cognome dell’utente con
print(user.last_name)
. Quel valoreuser
haUser
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’oggettouser
in un oggetto JSONData
- Finalmente, si converte l’oggetto
Data
inString
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 aCodable
, 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à chiamatausers
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
eJSONEncoder
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