Codable conformance for Swift enums

Enums in Swift are a fundamental way to model a fixed set of choices or states in a type-safe manner. Adding Codable conformance allows enums to be serialized and deserialized, enabling them to work seamlessly with data formats like JSON or property lists. This is particularly useful when interacting with APIs, saving app data, or exchanging information between systems.

# Automatic Codable conformance

In many cases, all we need to do is mark an enum as Codable, and the compiler handles the rest. Before Swift 5.5, only enums conforming to RawRepresentable could automatically conform to Codable. Now, enums without raw values, including those with or without associated values, can also benefit from automatic Codable synthesis if all associated types are themselves Codable.

Let’s look at how automatic conformance works for different kinds of enums.

# Raw value enums

For enums with raw values, the encoded value corresponds to the raw value that represents it.

enum Direction: String, Codable {
    case north
    case south
    case east
    case west
}

let direction: Direction = .north
let jsonData = try JSONEncoder().encode(direction)

// Output: "north"
let jsonString = String(data: jsonData, encoding: .utf8)!

Decoding works the same way, the raw value is mapped back to the appropriate case.

# Enums without raw or associated values

Enums without associated values are encoded as an empty container, preserving the case name as the key.

enum Status: Codable {
    case success
    case failure
}

let status: Status = .success
let jsonData = try JSONEncoder().encode(status)

// Output: {"success":{}}
let jsonString = String(data: jsonData, encoding: .utf8)!

Decoding restores the enum case from the JSON key.

# Enums with associated values

For enums with associated values, each case is treated as a container with a key matching the case name. The associated values are encoded as nested key-value pairs within that container.

enum Command: Codable {
    case load(key: String)
    case store(key: String, value: Int)
}

let command: Command = .store(key: "exampleKey", value: 42)
let jsonData = try JSONEncoder().encode(command)

// Output: {"store":{"value":42,"key":"exampleKey"}}
let jsonString = String(data: jsonData, encoding: .utf8)!

For cases with unlabeled associated values, the compiler generates underscore-prefixed numeric keys, such as _0, _1, etc., based on their position.

enum Role: Codable {
    case vipMember(String, Int)
}


let role: Role = .vipMember("1234", 5)
let jsonData = try JSONEncoder().encode(role)

// Output: {"vipMember":{"_0":"1234","_1":5}}
let jsonString = String(data: jsonData, encoding: .utf8)!

Optional associated values are treated as nil if they are missing, which results in them being excluded from the encoded data.

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Level up your Swift skills!$35

100+ tips to take your Swift code to the next level

Swift Gemsby Natalia Panferova

  • Advanced Swift techniques for experienced developers bypassing basic tutorials
  • Curated, actionable tips ready for immediate integration into any Swift project
  • Strategies to improve code quality, structure, and performance across all platforms

Level up your Swift skills!

100+ tips to take your Swift code to the next level

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Swift Gems

by Natalia Panferova

$35

# Customizing automatic conformance

# Customizing case names

By defining a CodingKeys enum, we can map case names to custom keys during encoding and decoding.

enum Status: Codable {
    case success
    case failure(reason: String)
    
    enum CodingKeys: String, CodingKey {
        case success
        case failure = "error"
    }
}

This customization alters the JSON structure:

let status: Status = .failure(reason: "Invalid request")
let jsonData = try JSONEncoder().encode(status)

// Output: {"error":{"reason":"Invalid request"}}
let jsonString = String(data: jsonData, encoding: .utf8)!

# Customizing associated value keys

We can also customize the keys used for associated values by defining separate coding keys enums for each case. These coding keys enums have to be prefixed with the capitalized case name.

enum Command: Codable {
    case load(key: String)
    case store(key: String, value: Int)
    
    enum StoreCodingKeys: String, CodingKey {
        case key
        case value = "data"
    }
}

let command: Command = .store(key: "code", value: 123)
let jsonData = try JSONEncoder().encode(command)

// Output: {"store":{"key":"code","data":123}}
let jsonString = String(data: jsonData, encoding: .utf8)!

This approach is particularly useful when we need to align the encoded structure with external data formats.

# Excluding cases or values

Certain cases or associated values can be excluded by omitting them from the CodingKeys declaration.

enum Event: Codable {
    case start
    case end(description: String, metadata: String = "")

    enum EndCodingKeys: String, CodingKey {
        case description
    }
}

Values that are excluded must have a default value defined, if a Decodable conformance should be synthesized.

Integrating SwiftUI into UIKit Apps by Natalia Panferova book coverIntegrating SwiftUI into UIKit Apps by Natalia Panferova book cover

Enhance older apps with SwiftUI!$45

A detailed guide on gradually adopting SwiftUI in UIKit projects

Updated for iOS 18 and Xcode 16!

Integrating SwiftUI into UIKit Appsby Natalia Panferova

  • Upgrade your apps with new features like Swift Charts and Widgets
  • Support older iOS versions with effective backward-compatible strategies
  • Seamlessly bridge state and data between UIKit and SwiftUI using the latest APIs

Enhance older apps with SwiftUI!

A detailed guide on gradually adopting SwiftUI in UIKit projects

Integrating SwiftUI into UIKit Apps by Natalia Panferova book coverIntegrating SwiftUI into UIKit Apps by Natalia Panferova book cover

Integrating SwiftUI
into UIKit Apps

by Natalia Panferova

Updated for iOS 18 and Xcode 16!

$45

# Custom Codable conformance

Automatic conformance doesn’t fit all use cases. For example, enums with overloaded case identifiers or complex encoding requirements might require custom implementations of encode(to:) and init(from:).

enum Response: Codable {
    case success(data: String)
    case error(reason: String)
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .success(let data):
            try container.encode(["status": "success", "data": data])
        case .error(let reason):
            try container.encode(["status": "error", "reason": reason])
        }
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let rawData = try container.decode([String: String].self)
        if let data = rawData["data"] {
            self = .success(data: data)
        } else if let reason = rawData["reason"] {
            self = .error(reason: reason)
        } else {
            throw DecodingError.dataCorrupted(
                DecodingError.Context(
                    codingPath: decoder.codingPath,
                    debugDescription: "Invalid data"
                )
            )
        }
    }
}

This allows a custom structure that doesn’t follow the default nested encoding format.

Codable conformance makes enums in Swift even more powerful and versatile. While automatic synthesis covers many scenarios, customization and fully manual implementations offer the flexibility to handle complex requirements. With these tools, we can work confidently with serialized data, ensuring that our enums integrate seamlessly into real-world applications.


As someone who has worked extensively with Swift, I've gathered many insights over the years. I've compiled them in my book Swift Gems, which is packed with advanced tips and techniques to help intermediate and advanced Swift developers enhance their coding skills. From optimizing collections and handling strings to mastering asynchronous programming and debugging, "Swift Gems" provides practical advice to elevate your Swift development. Grab your copy of Swift Gems and let's explore the advanced techniques together.

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Level up your Swift skills!$35

100+ tips to take your Swift code to the next level

Swift Gemsby Natalia Panferova

  • Advanced Swift techniques for experienced developers bypassing basic tutorials
  • Curated, actionable tips ready for immediate integration into any Swift project
  • Strategies to improve code quality, structure, and performance across all platforms

Level up your Swift skills!

100+ tips to take your Swift code to the next level

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Swift Gems

by Natalia Panferova

$35