cat swift/codable-avanzato-chiavi-date-valori-mancanti.md
Codable avanzato: chiavi, date e valori mancanti
Codable diventa davvero utile quando sai gestire CodingKeys, date, snake_case, decodeIfPresent e decoding custom.
Codable è uno degli strumenti più comodi di Swift, ma gli esempi base non bastano. Nel mondo reale devi gestire chiavi diverse, date, valori mancanti, default e conversioni.
CodingKeys
struct ProductDTO: Decodable {
let id: String
let name: String
let priceCents: Int
enum CodingKeys: String, CodingKey {
case id
case name
case priceCents = "price_cents"
}
}
Questo evita di portare snake_case dentro il codice Swift.
keyDecodingStrategy
Puoi configurare il decoder:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Allora price_cents può diventare priceCents automaticamente.
Usa questa strategia se la API è coerente. Se ci sono eccezioni o chiavi strane, CodingKeys esplicito può essere più sicuro.
Date
Date ISO 8601:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
DTO:
struct ProductDTO: Decodable {
let createdAt: Date
}
Se la API usa formati custom, devi configurare un DateFormatter o una strategia custom.
decodeIfPresent
Quando implementi decoding manuale:
struct ProductDTO: Decodable {
let id: String
let description: String?
enum CodingKeys: String, CodingKey {
case id
case description
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
description = try container.decodeIfPresent(String.self, forKey: .description)
}
}
decodeIfPresent restituisce nil se la chiave manca o se il valore è null.
Default values
Supponiamo che tags possa mancare:
struct ProductDTO: Decodable {
let id: String
let tags: [String]
enum CodingKeys: String, CodingKey {
case id
case tags
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
}
}
Usa default solo se il significato è corretto. Un array mancante non è sempre uguale a un array vuoto.
Numeri come stringhe
API fragile:
{ "price_cents": "2900" }
Decoding custom:
let rawPrice = try container.decode(String.self, forKey: .priceCents)
guard let priceCents = Int(rawPrice) else {
throw DecodingError.dataCorruptedError(
forKey: .priceCents,
in: container,
debugDescription: "price_cents must be an integer string"
)
}
Non ingoiare silenziosamente dati corrotti se il campo è essenziale.
Decoding error leggibili
DecodingError contiene informazioni utili:
- key not found;
- type mismatch;
- value not found;
- data corrupted;
- coding path.
Quando fai debug, stampa l’errore completo. Non trasformarlo subito in “errore generico”.
DTO robusto
struct ProductDTO: Decodable {
let id: String
let name: String
let priceCents: Int
let createdAt: Date
let tags: [String]
enum CodingKeys: String, CodingKey {
case id
case name
case priceCents = "price_cents"
case createdAt = "created_at"
case tags
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
priceCents = try container.decode(Int.self, forKey: .priceCents)
createdAt = try container.decode(Date.self, forKey: .createdAt)
tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
}
}
Checklist
Quando configuri Codable:
- uso
CodingKeysoconvertFromSnakeCase? - le date hanno strategia chiara?
- quali chiavi possono mancare?
- quali
nilsono accettabili? - quali default sono corretti?
- devo gestire tipi incoerenti?
- gli errori di decoding arrivano fino a dove posso diagnosticarli?
Esercizio
Crea un DTO che decodifica:
{
"id": "p-1",
"name": "Manuale Swift",
"price_cents": 2900,
"created_at": "2026-05-04T08:00:00Z",
"tags": null
}
tags deve diventare [].
Poi modifica il JSON con price_cents stringa e implementa decoding custom.