Skip to main content

Working with Codable

1. Conforming to Codable Protocol

Property Type Description
Codable protocol Combines Encodable and Decodable protocols
Encodable protocol Defines types that can be encoded to external representation (e.g., JSON)
Decodable protocol Defines types that can be decoded from external representation (e.g., JSON)

Example: Basic Codable Conformance

struct Person: Codable {
    let name: String
    let age: Int
}

Example: Custom Property Names

struct User: Codable {
    @SerializedName("full_name")
    let name: String
    let email: String
}
Note: Codable is a type alias for Encodable & Decodable. It allows Swift types to be encoded and decoded using JSONEncoder and JSONDecoder.

2. Using JSONEncoder to Encode

Property Value Description
encode(_:) func encode<T>(value: T) throws -> Data Encodes a value into JSON data
outputFormatting JSONEncoder.OutputFormatting Controls formatting of the output JSON (e.g. pretty-printing)
dateEncodingStrategy JSONEncoder.DateEncodingStrategy Defines how dates are encoded (e.g. as ISO8601, milliseconds)
dataEncodingStrategy JSONEncoder.DataEncodingStrategy Defines how Data is encoded (e.g. base64)

Example: Encoding a Person Object

import Foundation

struct Person: Codable {
    let name: String
    let age: Int
    let birthDate: Date
}

let person = Person(name: "Alice", age: 30, birthDate: Date())

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

do {
    let jsonData = try encoder.encode(person)
    print(String(data: jsonData, encoding: .utf8)!)
} catch {
    print("Encoding failed: \(error)")
}
Note: The encode(_:) method throws an error if encoding fails, so always wrap it in a do-catch block.

Key Points

  • JSONEncoder is used to convert Swift objects into JSON data.
  • It supports customizing output formatting, date encoding, and data encoding strategies.
  • Encoding can fail due to invalid data or unsupported types, so error handling is crucial.

3. Using JSONDecoder to Decode

Method Description Example
decode(_:from:) Decodes a JSON object into a Swift type
let json = "{\\"name\\": \\"John\\", \\"age\\": 30}".data(using: .utf8)!
let person = try JSONDecoder().decode(Person.self, from: json)
decode(_:from:) Decodes a JSON array into an array of Swift types
let jsonArray = "[{\\"name\\": \\"John\\", \\"age\\": 30}, {\\"name\\": \\"Jane\\", \\"age\\": 25}]".data(using: .utf8)!
let people = try JSONDecoder().decode([Person].self, from: jsonArray)

Example: Decoding a JSON Object

struct Person: Codable {
    let name: String
    let age: Int
}

let json = "{\\"name\\": \\"John\\", \\"age\\": 30}".data(using: .utf8)!
let person = try JSONDecoder().decode(Person.self, from: json)
print(person.name) // John

Example: Decoding a JSON Array

let jsonArray = "[{\\"name\\": \\"John\\", \\"age\\": 30}, {\\"name\\": \\"Jane\\", \\"age\\": 25}]".data(using: .utf8)!
let people = try JSONDecoder().decode([Person].self, from: jsonArray)
print(people.count) // 2

4. Customizing Coding Keys

Property Value Description
CodingKey String Used to define the keys for encoding and decoding values
Key String Represents the key used in a dictionary or JSON object

Example: Custom Coding Keys

struct User: Codable {
    let name: String
    let age: Int
    let email: String

    enum CodingKeys: String, CodingKey {
        case name = "full_name"
        case age = "user_age"
        case email = "email_address"
    }
}
Note: When using custom CodingKeys, ensure that all properties are mapped to a corresponding key in the enum.

5. Using Custom Encoding Logic

Method Description Example
encode(to:) Custom encoding logic for a type
struct Person: Codable {
    let name: String
    let age: Int

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(age, forKey: .age)
    }
}

enum CodingKeys: String, CodingKey {
    case name
    case age
}

Example: Custom Encoding with Nested Objects

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

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(street, forKey: .street)
        try container.encode(city, forKey: .city)
    }
}

struct User: Codable {
    let name: String
    let address: Address

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(address, forKey: .address)
    }
}
Note: When implementing custom encoding logic, ensure all nested types also conform to Codable.

6. Using Custom Decoding Logic

Method Description Example
init(from: Decoder) Custom decoding logic using a custom initializer
struct User: Codable {
    let name: String
    let age: Int

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Int.self, forKey: .age)
    }
}

Example: Custom Decoding with Conditional Logic

struct Product: Codable {
    let id: Int
    let name: String
    let price: Double

    enum CodingKeys: String, CodingKey {
        case id, name, price
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        price = try container.decode(Double.self, forKey: .price)
    }
}
Note: When implementing custom decoding logic, ensure all required fields are handled properly to avoid runtime errors.

7. Handling Nested JSON

Property Type Description Example
nestedObject MyNestedType Property containing a nested object
{
  "nestedObject": {
    "name": "John",
    "age": 30
  }
}
nestedArray [String] Property containing an array of nested objects
{
  "nestedArray": [
    {
      "id": "1",
      "value": "test"
    }
  ]
}

Example: Decoding Nested JSON

struct User: Codable {
    let name: String
    let age: Int
    let address: Address
}

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

let jsonData = """
{
  "name": "John",
  "age": 30,
  "address": {
    "street": "123 Main St",
    "city": "Anytown"
  }
}
""".data(using: .utf8)!

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

8. Working with Dates

Property Type Notes
Date Date Standard Swift type for representing dates and times
ISO8601DateFormatter ISO8601DateFormatter Formatter for ISO 8601 date strings (e.g., "2025-04-05T14:30:00Z")
DateFormatter DateFormatter Customizable date formatter for various formats

Example: Encoding and decoding a Date

import Foundation

struct Event: Codable {
    let name: String
    let date: Date
}

let event = Event(name: "Swift Conference", date: Date())

// Encoding to JSON
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let jsonData = try encoder.encode(event)

// Decoding from JSON
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let decodedEvent = try decoder.decode(Event.self, from: jsonData)
Note: By default, Date is encoded as a double (Unix timestamp). Use .iso8601 for human-readable date strings.

9. Handling Missing or Optional Fields

Property Type Description Default
CodingKey String Key used to encode/decode values N/A
Optional Optional<T> Wraps a type to allow missing values nil

Example: Optional Field

struct User: Codable {
    let name: String
    let age: Int?
}

let json = "{"name": "John"}".data(using: .utf8)!
let user = try! JSONDecoder().decode(User.self, from: json)
print(user.age) // Optional<Int>(nil)

Example: Default Value for Missing Field

struct User: Codable {
    let name: String
    let age: Int = 0
}

let json = "{"name": "John"}".data(using: .utf8)!
let user = try! JSONDecoder().decode(User.self, from: json)
print(user.age) // 0
Note: When a field is missing in the JSON, Swift will use the default value if one is provided. Otherwise, it will be nil for optional types.

10. Using PropertyListEncoder and PropertyListDecoder

Encoder/Decoder Format Description
PropertyListEncoder plist Encodes Swift objects into Property List format
PropertyListDecoder plist Decodes Property List data into Swift objects

Example: Encoding to Property List

import Foundation

struct Person: Codable {
    let name: String
    let age: Int
}

let person = Person(name: "Alice", age: 30)

let encoder = PropertyListEncoder()
encoder.outputFormat = .xml

do {
    let data = try encoder.encode(person)
    print(String(data: data, encoding: .utf8)!)
} catch {
    print("Encoding error: " + error.localizedDescription)
}

Example: Decoding from Property List

import Foundation

struct Person: Codable {
    let name: String
    let age: Int
}

let plistData = <#Property List Data#>

let decoder = PropertyListDecoder()
decoder.format = .xml

do {
    let person = try decoder.decode(Person.self, from: plistData)
    print("Name: " + person.name)
} catch {
    print("Decoding error: " + error.localizedDescription)
}
Note: PropertyListEncoder and PropertyListDecoder support XML and binary formats. The default format is .xml.

Was this article helpful?