Swift Codable

Codable converts Swift values to and from formats such as JSON. Apps talk to servers in JSON, and Codable handles the translation with almost no code. Swift builds the conversion steps automatically when every property of your type is itself convertible.

Picture a translator between two people who speak different languages. Your app speaks Swift structs, and the internet speaks JSON text. Codable plays the translator in both directions.

Diagram: Two-Way Translation

   JSON text                           Swift struct
 {"id": 7,         --- decode -->      User(id: 7,
  "name": "Ava"}                            name: "Ava")

 {"id": 7,         <-- encode ---       User(id: 7,
  "name": "Ava"}                            name: "Ava")

Making a Type Codable

Add the Codable protocol to a struct. The protocol combines Encodable for writing and Decodable for reading.

import Foundation

struct User: Codable {
    let id: Int
    let name: String
    let email: String?
}

The optional email property accepts JSON that leaves the field out.

Decoding JSON

Turn JSON text into Data and ask JSONDecoder to build the struct.

let json = """
{"id": 7, "name": "Ava", "email": "ava@example.com"}
"""
let data = Data(json.utf8)

do {
    let user = try JSONDecoder().decode(User.self, from: data)
    print(user.name)   // Output: Ava
} catch {
    print("Decoding failed: \(error)")
}

Decoding can fail. The call needs try inside a do-catch block.

Encoding to JSON

let user = User(id: 7, name: "Ava", email: "ava@example.com")

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

if let output = try? encoder.encode(user),
   let text = String(data: output, encoding: .utf8) {
    print(text)
}
// Output:
// {
//   "email" : "ava@example.com",
//   "id" : 7,
//   "name" : "Ava"
// }

Custom Key Names

Servers often use names like unit_price, which break Swift naming style. A nested CodingKeys enumeration maps the names.

struct Product: Codable {
    let title: String
    let unitPrice: Double

    enum CodingKeys: String, CodingKey {
        case title
        case unitPrice = "unit_price"
    }
}

let raw = Data(#"{"title": "Notebook", "unit_price": 3.5}"#.utf8)
let item = try JSONDecoder().decode(Product.self, from: raw)
print(item.unitPrice)   // Output: 3.5

A decoder setting offers a shortcut for the common case. Set keyDecodingStrategy to .convertFromSnakeCase and Swift maps every snake_case key to camelCase.

Nested Data and Arrays

Codable handles nested structs and arrays without extra code. Each nested type only needs to adopt Codable too.

struct Team: Codable {
    let name: String
    let members: [User]
}

let teamJSON = Data("""
{"name": "Blue", "members": [
  {"id": 1, "name": "Asha"},
  {"id": 2, "name": "Ben"}
]}
""".utf8)

let team = try JSONDecoder().decode(Team.self, from: teamJSON)
print(team.members.count)   // Output: 2

Understanding Decoding Errors

A wrong type or a missing field throws a DecodingError. The error message names the field, which helps with debugging.

let broken = Data(#"{"id": "seven", "name": "Ava"}"#.utf8)

do {
    _ = try JSONDecoder().decode(User.self, from: broken)
} catch let DecodingError.typeMismatch(type, context) {
    print("Expected \(type) at \(context.codingPath.map(\.stringValue))")
}
// Output: Expected Int at ["id"]

Dates and Special Values

Servers send dates as text. A decoder converts that text into a Date value when you choose a date strategy. The setting .iso8601 reads the common international format, such as 2026-11-12T09:30:00Z.

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase

These two lines remove most manual key mapping and date parsing from a typical networking layer.

Saving Data to Disk

Codable supports storage on the device. Encode a struct to Data and write the bytes to a file. Load the file later and decode the bytes to restore the struct. Apps use this pattern for settings, drafts, and offline caches.

Common Mistakes

A property type that does not match the JSON triggers a decoding error. A JSON number cannot fill a String property, and a JSON string cannot fill an Int property. Mark a property optional when the server may leave it out.

Property names must match the JSON keys exactly, including capital letters. A key named UserName needs a coding key that points to userName, unless a decoder strategy handles the conversion.

Real-World Use

Weather apps, news readers, and online stores all receive JSON from servers. Each response becomes a Swift struct through a single decode call. Developers work with typed values, and the compiler warns about mistakes before users see them.

Optional Fields and Default Values

Servers evolve. A new version of an API may add fields that old apps never read, and Codable ignores unknown fields without complaint. A field that the app expects but the server omits triggers an error unless the property is optional. Many teams mark rarely present fields as optional and supply a fallback value at the point of use.

Keep the Codable struct as a faithful mirror of the server response. Convert it into a friendlier model for the screen in a separate step. This habit isolates server changes in one place.

Key Takeaways

  • Codable combines encoding and decoding in one protocol.
  • JSONDecoder reads JSON and JSONEncoder writes JSON.
  • CodingKeys maps JSON names to Swift property names.
  • Nested structs and arrays work automatically when each type is codable.