Codable cheat sheet
Codable
è stata una delle caratteristiche fondamentali di Swift 4.0, portando con sé una conversione incredibilmente fluida tra tipi di dati Swift e JSON. Poi è migliorata ulteriormente in Swift 4.1 grazie all’aggiunta di nuove funzionalità, e mi aspetto cose ancora più grandi in futuro.
In questo articolo voglio fornire esempi rapidi di codice per aiutarvi a rispondere a domande comuni e risolvere problemi comuni, il tutto utilizzando Codable
.
SPONSORED Costruire e mantenere un’infrastruttura di sottoscrizione in-app è difficile. Per fortuna c’è un modo migliore. Con RevenueCat, puoi implementare gli abbonamenti per la tua app in ore, non in mesi, così puoi tornare a costruire la tua app.
Provalo gratis
Sponsorizza Hacking with Swift e raggiungi la più grande comunità Swift del mondo!
Codifica e decodifica JSON
Iniziamo con le basi: convertire un po’ di JSON in strutture Swift.
Primo, ecco un po’ di JSON con cui lavorare:
let json = """"""let data = Data(json.utf8)
L’ultima riga lo converte in un oggetto Data
perché è quello con cui lavorano i decoder Codable
.
Poi abbiamo bisogno di definire una struct Swift che conterrà i nostri dati finiti:
struct User: Codable { var name: String var age: Int}
Ora possiamo andare avanti ed eseguire la decodifica:
let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}
Questo stamperà “Paul”, che è il nome del primo utente nel JSON.
Convertire il caso
Un problema comune con JSON è che userà una formattazione diversa per i suoi nomi chiave di quella che vogliamo usare in Swift. Per esempio, potreste avere “first_name” nel vostro JSON e avere bisogno di convertirlo in una proprietà firstName
.
Ora, una soluzione ovvia qui è solo quella di cambiare sia il JSON che i vostri tipi Swift in modo che usino la stessa convenzione di denominazione, ma non lo faremo qui. Invece, assumerò che tu abbia un codice come questo:
let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}
Per far funzionare questo abbiamo bisogno di cambiare solo una proprietà nel nostro decodificatore JSON:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase
Che istruisce Swift a mappare i nomi in caso serpente (names_written_like_this) in nomi in caso cammello (namesWrittenLikeThis).
Mappatura di nomi di chiavi diverse
Se avete chiavi JSON che sono completamente diverse dalle vostre proprietà Swift, potete mapparle usando un CodingKeys
enum.
Date un’occhiata a questo JSON:
let json = """"""
Questi nomi chiave non sono un granché, e in realtà ci piacerebbe convertire quei dati in una struct come questa:
struct User: Codable { var firstName: String var lastName: String var age: Int}
Per far sì che ciò accada abbiamo bisogno di dichiarare un CodingKeys
enum: una mappatura che Codable
può usare per convertire i nomi JSON in proprietà della nostra struct. Questo è un enum regolare che usa stringhe per i suoi valori grezzi in modo che possiamo specificare sia il nome della nostra proprietà (il caso enum) che il nome JSON (il valore enum) allo stesso tempo. Ha anche bisogno di essere conforme al protocollo CodingKey
, che è ciò che fa funzionare questo con il protocollo Codable
.
Quindi, aggiungete questo enum alla struct:
enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}
Che ora sarà in grado di decodificare il JSON come previsto.
Nota: l’enum si chiama CodingKeys
e il protocollo si chiama CodingKey
.
Lavorare con le date ISO-8601
Ci sono molti modi di lavorare con le date su internet, ma ISO-8601 è il più comune. Codifica la data completa nel formato YYYY-MM-DD, poi la lettera “T” per segnalare l’inizio delle informazioni temporali, poi l’ora nel formato HH:MM:SS, e infine un fuso orario. Il fuso orario “Z”, abbreviazione di “Zulu time” è comunemente usato per indicare UTC.
Codable
è in grado di gestire ISO-8601 con un convertitore di data integrato. Quindi, dato questo JSON:
let json = """"""
Possiamo decodificarlo in questo modo:
struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601
Questo permette l’analisi della data ISO-8601, che converte da 1999-04-03T17:30:31Z in un’istanza Date
, gestendo anche la conversione da caso serpente a caso cammello.
Lavorare con altre date comuni
Swift è dotato di un supporto integrato per altri tre importanti formati di data. Li usate proprio come usate le date ISO-8601 come mostrato sopra, quindi ne parlerò brevemente:
- Il formato
.deferredToDate
è il formato di data di Apple, e traccia il numero di secondi e millisecondi dal 1 gennaio 2001. Non è molto utile al di fuori delle piattaforme Apple. - Il formato
.millisecondsSince1970
traccia il numero di secondi e millisecondi dal 1° gennaio 1970. Questo è abbastanza comune online. - Il formato
.secondsSince1970
traccia il numero di secondi interi dal 1° gennaio 1970. Questo è estremamente comune online, ed è secondo solo a ISO-8601.
Lavorare con date personalizzate
Se il vostro formato di data non corrisponde a una delle opzioni incorporate, non disperate: Codable può analizzare date personalizzate basate su un formattatore di date creato da voi.
Per esempio, questo JSON traccia il giorno in cui uno studente si è laureato all’università:
let json = """"""
Questo usa il formato di data DD-MM-YYYY, che non è una delle opzioni integrate di Swift. Fortunatamente, puoi fornire un’istanza preconfigurata di DateFormatter
come strategia di decodifica della data, come questa:
let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)
Lavorare con date strane
A volte avrai date così strane che nemmeno DateFormatter
può gestirle. Per esempio, potresti ottenere JSON che memorizza le date usando il numero di giorni trascorsi dal 1 gennaio 1970:
let json = """"""
Per far funzionare questo abbiamo bisogno di scrivere un decoder personalizzato per la data. Tutto il resto sarà ancora gestito da Codable
– stiamo solo fornendo una chiusura personalizzata che elaborerà la parte relativa alle date.
Potreste provare a fare un po’ di matematica, come moltiplicare il conteggio dei giorni per 86400 (il numero di secondi in un giorno), poi usare il metodo addTimeInterval()
di Date
. Tuttavia, questo non terrà conto dell’ora legale e di altri problemi di data, quindi una soluzione migliore è usare DateComponents
e Calendar
come questa:
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()}
Attenzione: Se dovete analizzare molte date, ricordate che questa chiusura verrà eseguita per ogni singola data – rendetela veloce!
Eseguire i dati gerarchici nel modo più semplice
Ogni JSON non banale è probabile che abbia dati gerarchici – un insieme di dati annidati dentro un altro. Per esempio:
let json = """"""
Codable
è in grado di gestirlo bene, a patto che si possano descrivere chiaramente le relazioni.
Io trovo che il modo più semplice per farlo sia usare strutture annidate, come questo:
struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}
Lo svantaggio è che se volete leggere il nome di un utente, dovete usare user.name.firstName
, ma almeno il lavoro di analisi è banale – il nostro codice esistente funziona già!
Eseguire l’analisi dei dati gerarchici nel modo più difficile
Se vuoi analizzare i dati gerarchici in una struttura piatta – cioè, volete essere in grado di scrivere user.firstName
piuttosto che user.name.firstName
– allora dovete fare un po’ di analisi voi stessi. Questo non è troppo difficile, però, e Codable
lo rende meravigliosamente sicuro per il tipo.
Prima di tutto, create la struttura che volete ottenere:
struct User: Codable { var firstName: String var lastName: String var age: Int}
In secondo luogo, abbiamo bisogno di definire le chiavi di codifica che descrivono dove i dati possono essere trovati nella gerarchia.
Guardiamo di nuovo il JSON:
let json = """"""
Come potete vedere, alla radice c’è una chiave chiamata “name” e un’altra chiamata “age”, quindi abbiamo bisogno di aggiungerle come chiavi di codifica. Metti questo all’interno della tua struct:
enum CodingKeys: String, CodingKey { case name, age}
All’interno di “name” ci sono altre due chiavi, “first_name” e “last_name”, quindi creeremo delle chiavi di codifica per queste due. Aggiungi questo:
enum NameCodingKeys: String, CodingKey { case firstName, lastName}
Ora la parte difficile: dobbiamo scrivere un inizializzatore personalizzato e un metodo di codifica personalizzato per il nostro tipo. Iniziate aggiungendo questo metodo vuoto alla vostra struct:
init(from decoder: Decoder) throws {}
Lì dentro, la prima cosa che dobbiamo fare è tentare di estrarre un contenitore che possiamo leggere usando le chiavi del nostro enum CodingKeys
, come questo:
let container = try decoder.container(keyedBy: CodingKeys.self)
Una volta fatto questo possiamo tentare di leggere la nostra proprietà age
. Questo è fatto in un modo type-safe: gli dite il tipo che volete decodificare (Int.self
per la nostra età), insieme ad un nome chiave dall’enum CodingKeys
:
age = try container.decode(Int.self, forKey: .age)
Poi dobbiamo scendere di un livello per leggere i dati del nostro nome. Come avete visto prima, “name” è una chiave di primo livello all’interno del nostro enum CodingKeys
, ma è in realtà un contenitore annidato di altri valori che abbiamo bisogno di leggere all’interno. Quindi, abbiamo bisogno di estrarre quel contenitore:
let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
E infine possiamo leggere due stringhe per le chiavi .firstName
e .lastName
:
firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)
Questo finisce l’inizializzatore personalizzato, ma abbiamo ancora un metodo da scrivere: encode(to:)
. Questo è effettivamente l’inverso dell’inizializzatore che abbiamo appena scritto, perché il suo compito è quello di riconvertire le nostre proprietà in contenitori annidati come appropriato.
Questo significa creare un contenitore basato sul nostro enum CodingKeys
e scrivere age
lì, poi creare un contenitore annidato basato sul nostro enum NameCodingKeys
, e scrivere sia firstName
che lastName
lì:
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)}
Questo completa tutto il nostro codice. Con questo in atto, è ora possibile leggere direttamente la proprietà firstName
, che è molto più bello!
SPONSORED Costruire e mantenere un’infrastruttura di abbonamento in-app è difficile. Per fortuna c’è un modo migliore. Con RevenueCat, puoi implementare gli abbonamenti per la tua app in ore, non in mesi, così puoi tornare a costruire la tua app.
Provalo gratis
Sponsorizza Hacking with Swift e raggiungi la più grande comunità Swift del mondo!
Leave a Reply