Skip to content
beginner Phase 5 · Networking & Data

JSON & Codable

Decode JSON with Codable, handle custom keys, nested objects, and date strategies.

45m
3 problems
Topic Progress 0%

Codable Protocol

What is Codable?

Codable is a type alias for Encodable and Decodable protocols combined. When a type conforms to Codable, Swift can automatically convert it to and from JSON (or other formats like plist).

Basic Conformance

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

With just this declaration, Swift generates the encoding and decoding logic automatically.

Decoding JSON

Convert JSON data into Swift objects:

let json = """
{
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "isActive": true
}
"""

let data = Data(json.utf8)
let user = try JSONDecoder().decode(User.self, from: data)
print(user.name) // Alice

Encoding to JSON

Convert Swift objects to JSON data:

let user = User(id: 1, name: "Alice", email: "alice@example.com", isActive: true)
let data = try JSONEncoder().encode(user)
let jsonString = String(data: data, encoding: .utf8)

Arrays of Objects

Decode arrays of objects:

let jsonArray = """
[
    {"id": 1, "name": "Alice", "email": "a@b.com", "isActive": true},
    {"id": 2, "name": "Bob", "email": "b@c.com", "isActive": false}
]
"""

let users = try JSONDecoder().decode([User].self, from: Data(jsonArray.utf8))

Optional Properties

Handle missing JSON keys with optionals:

struct Product: Codable {
    let id: Int
    let name: String
    let description: String?
    let discount: Double?
}

If description or discount is missing from the JSON, they become nil in Swift.

Nested Objects

Handle nested JSON structures:

struct Address: Codable {
    let street: String
    let city: String
    let zipCode: String
}

struct Company: Codable {
    let name: String
    let address: Address
    let employeeCount: Int
}

JSON like {"name": "Acme", "address": {"street": "123 Main", "city": "NYC", "zipCode": "10001"}, "employeeCount": 50} decodes directly.

Custom Keys

Why Custom Keys?

JSON keys often use snake_case or different naming conventions than Swift. CodingKeys lets you map between JSON key names and Swift property names.

Basic CodingKeys

struct UserProfile: Codable {
    let id: Int
    let firstName: String
    let lastName: String
    let emailAddress: String
    let createdAt: Date

    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case emailAddress = "email_address"
        case createdAt = "created_at"
    }
}

Using keyDecodingStrategy

Instead of individual CodingKeys, set a strategy on the decoder:

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

let user = try decoder.decode(UserProfile.self, from: data)

This automatically converts first_name to firstName.

keyEncodingStrategy

When encoding, convert Swift camelCase to snake_case:

let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase

let data = try encoder.encode(user)

Custom Conversion Keys

For complex transformations, use a custom key strategy:

enum CustomKeyStrategy: JSONDecoder.KeyDecodingStrategy {
    static func convert(_ key: CodingKey) -> CodingKey {
        let lowered = key.stringValue.lowercased()
        return lowered as CodingKey
    }
}

Container Types

CodingKeys works with nested containers:

struct APIResponse: Codable {
    let status: String
    let data: UserData
    let pagination: PaginationInfo?

    enum CodingKeys: String, CodingKey {
        case status, data, pagination
    }
}

Date Strategies

Date Decoding Strategies

JSON dates come in many formats. JSONDecoder provides built-in strategies for common formats.

Default Strategy (Unix Timestamp)

By default, dates are decoded as Unix timestamps (seconds since 1970):

let decoder = JSONDecoder()
// Uses .secondsSince1970 by default

ISO 8601 Strategy

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

Handles dates like 2024-01-15T10:30:00Z.

Formatted Date Strategy

let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .formatted(formatter)

Custom Strategy

For complex date formats:

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom { decoder in
    let container = try decoder.singleValueContainer()
    let dateString = try container.decode(String.self)

    let formatter = DateFormatter()
    formatter.dateFormat = "dd/MM/yyyy HH:mm"
    formatter.locale = Locale(identifier: "en_US_POSIX")

    guard let date = formatter.date(from: dateString) else {
        throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date format")
    }
    return date
}

Date Encoding Strategies

Match your encoding strategy to your API's expected format:

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601

// Or custom
encoder.dateEncodingStrategy = .formatted(dateFormatter)

// Or milliseconds
encoder.dateEncodingStrategy = .millisecondsSince1970

Handling Multiple Formats

If your API uses different date formats in different endpoints:

struct APIParser {
    static let iso8601Decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        return decoder
    }()

    static let timestampDecoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .secondsSince1970
        return decoder
    }()
}

Quiz

1. What is Codable?

Question 1 options

2. How do you map snake_case JSON keys to camelCase Swift properties?

Question 2 options

3. What date format does JSONDecoder use by default?

Question 3 options

4. How do you handle missing JSON keys in a Codable struct?

Question 4 options

Flashcards

Question

What is Codable?

Answer

A type alias for Encodable and Decodable, enabling automatic JSON serialization/deserialization.

Question

How do you use CodingKeys?

Answer

Define an enum conforming to CodingKey with String raw values to map JSON keys to Swift properties.

Question

What is the simplest way to handle snake_case JSON?

Answer

Set decoder.keyDecodingStrategy = .convertFromSnakeCase on JSONDecoder.

Question

How do you decode optional values from JSON?

Answer

Declare the Swift property as Optional (String?, Int?, etc.). Missing keys decode as nil.

Revision Notes

Key Takeaways

  • 1. Codable provides automatic JSON encoding/decoding with minimal boilerplate
  • 2. CodingKeys enum maps between different key naming conventions
  • 3. keyDecodingStrategy handles snake_case conversion automatically
  • 4. Optional properties gracefully handle missing JSON keys
  • 5. Configure date strategies to match your API's date format

Interview Tips

  • Explain how Codable automatically generates encoding/decoding
  • Know when to use CodingKeys vs keyDecodingStrategy
  • Describe how to handle different date formats from APIs
  • Discuss strategies for handling nested JSON and optional fields

Cheat Sheet

Codable = Encodable + Decodable.
JSONDecoder().decode(Type.self, from: data) - decode.
JSONEncoder().encode(instance) - encode.
CodingKeys enum maps JSON keys to Swift properties.
keyDecodingStrategy = .convertFromSnakeCase - auto snake_case.
dateDecodingStrategy: .iso8601, .secondsSince1970, .formatted(), .custom.