cat swift/codable-avanzato-chiavi-date-valori-mancanti.md

Lezione 49 55 min

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 CodingKeys o convertFromSnakeCase?
  • le date hanno strategia chiara?
  • quali chiavi possono mancare?
  • quali nil sono 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.