Swift
Codable
Dictionary
Encoding
Swift Programming

How can I use Swift's Codable to encode into a dictionary?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Swift's Codable protocol is designed for encoding to and decoding from external formats like JSON. There is no built-in DictionaryEncoder, but you can convert a Codable object to a [String: Any] dictionary by encoding it to JSON data first, then deserializing that data with JSONSerialization. For a cleaner approach, you can write a custom DictionaryEncoder that avoids the intermediate JSON step.

Method 1: JSON Round-Trip (Simple)

Encode to JSON, then deserialize to a dictionary:

swift
1import Foundation
2
3struct User: Codable {
4    let name: String
5    let age: Int
6    let email: String
7}
8
9let user = User(name: "Alice", age: 30, email: "[email protected]")
10
11// Encode to JSON Data
12let jsonData = try JSONEncoder().encode(user)
13
14// Deserialize to dictionary
15let dict = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
16print(dict!)
17// ["name": "Alice", "age": 30, "email": "[email protected]"]

This works for any Codable type. The downside is two serialization steps (encode to JSON bytes, then parse those bytes back to objects).

Method 2: Extension on Encodable (Reusable)

swift
1extension Encodable {
2    func asDictionary() throws -> [String: Any] {
3        let data = try JSONEncoder().encode(self)
4        guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
5            throw EncodingError.invalidValue(
6                self,
7                EncodingError.Context(
8                    codingPath: [],
9                    debugDescription: "Top-level value is not a dictionary"
10                )
11            )
12        }
13        return dict
14    }
15
16    func asArray() throws -> [[String: Any]] {
17        let data = try JSONEncoder().encode(self)
18        guard let array = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
19            throw EncodingError.invalidValue(
20                self,
21                EncodingError.Context(
22                    codingPath: [],
23                    debugDescription: "Top-level value is not an array"
24                )
25            )
26        }
27        return array
28    }
29}
30
31// Usage
32let user = User(name: "Alice", age: 30, email: "[email protected]")
33let dict = try user.asDictionary()
34print(dict["name"]!)  // Alice

Method 3: Custom DictionaryEncoder

For a direct conversion without JSON intermediary:

swift
1class DictionaryEncoder {
2    func encode<T: Encodable>(_ value: T) throws -> [String: Any] {
3        let data = try JSONEncoder().encode(value)
4        let jsonObject = try JSONSerialization.jsonObject(with: data)
5
6        guard let dict = jsonObject as? [String: Any] else {
7            throw EncodingError.invalidValue(
8                value,
9                EncodingError.Context(
10                    codingPath: [],
11                    debugDescription: "Expected dictionary, got \(type(of: jsonObject))"
12                )
13            )
14        }
15        return dict
16    }
17}
18
19class DictionaryDecoder {
20    func decode<T: Decodable>(_ type: T.Type, from dict: [String: Any]) throws -> T {
21        let data = try JSONSerialization.data(withJSONObject: dict)
22        return try JSONDecoder().decode(type, from: data)
23    }
24}
25
26// Usage
27let encoder = DictionaryEncoder()
28let decoder = DictionaryDecoder()
29
30let dict = try encoder.encode(user)
31let restored = try decoder.decode(User.self, from: dict)

Nested Objects

swift
1struct Address: Codable {
2    let street: String
3    let city: String
4    let zip: String
5}
6
7struct Employee: Codable {
8    let name: String
9    let department: String
10    let address: Address
11    let skills: [String]
12}
13
14let employee = Employee(
15    name: "Bob",
16    department: "Engineering",
17    address: Address(street: "123 Main St", city: "Seattle", zip: "98101"),
18    skills: ["Swift", "Kotlin", "Rust"]
19)
20
21let dict = try employee.asDictionary()
22print(dict)
23// ["name": "Bob", "department": "Engineering",
24//  "address": ["street": "123 Main St", "city": "Seattle", "zip": "98101"],
25//  "skills": ["Swift", "Kotlin", "Rust"]]
26
27// Access nested values
28let address = dict["address"] as? [String: Any]
29print(address?["city"]!)  // Seattle

Custom Coding Keys

swift
1struct APIRequest: Codable {
2    let userId: Int
3    let accessToken: String
4    let isActive: Bool
5
6    enum CodingKeys: String, CodingKey {
7        case userId = "user_id"
8        case accessToken = "access_token"
9        case isActive = "is_active"
10    }
11}
12
13let request = APIRequest(userId: 42, accessToken: "abc123", isActive: true)
14let dict = try request.asDictionary()
15print(dict)
16// ["user_id": 42, "access_token": "abc123", "is_active": true]

The dictionary keys follow the CodingKeys mapping, producing snake_case keys.

Date Encoding Strategies

swift
1struct Event: Codable {
2    let title: String
3    let date: Date
4}
5
6let event = Event(title: "Launch", date: Date())
7
8let encoder = JSONEncoder()
9encoder.dateEncodingStrategy = .iso8601
10
11let data = try encoder.encode(event)
12let dict = try JSONSerialization.jsonObject(with: data) as! [String: Any]
13print(dict["date"]!)  // "2025-03-02T10:15:32Z"

Using with Firestore / API Payloads

swift
1// Firebase Firestore expects [String: Any] dictionaries
2let user = User(name: "Alice", age: 30, email: "[email protected]")
3let dict = try user.asDictionary()
4
5// Firestore
6// db.collection("users").document("alice").setData(dict)
7
8// URLRequest body
9let jsonData = try JSONSerialization.data(withJSONObject: dict)
10var request = URLRequest(url: URL(string: "https://api.example.com/users")!)
11request.httpBody = jsonData
12request.httpMethod = "POST"
13request.setValue("application/json", forHTTPHeaderField: "Content-Type")

Dictionary Back to Codable

swift
1let dict: [String: Any] = [
2    "name": "Charlie",
3    "age": 25,
4    "email": "[email protected]"
5]
6
7let data = try JSONSerialization.data(withJSONObject: dict)
8let user = try JSONDecoder().decode(User.self, from: data)
9print(user.name)  // Charlie

Common Pitfalls

  • Date encoding defaults to Double: Without setting dateEncodingStrategy, JSONEncoder encodes Date as a TimeInterval (seconds since 1970). Set .iso8601 or .formatted for readable dates in your dictionary.
  • Any type is not Codable: You cannot encode [String: Any] with JSONEncoder because Any does not conform to Codable. Use JSONSerialization for untyped dictionaries.
  • Enums without raw values: Enums with associated values need custom Codable conformance. Without it, encoding fails at runtime.
  • Optional fields are omitted: By default, JSONEncoder omits nil optional properties. The resulting dictionary will not have keys for nil values. If you need explicit null, use custom encoding.
  • Float/Double precision: JSON serialization may introduce floating-point precision artifacts. 3.14 might become 3.1400000000000001 in the dictionary. Use Decimal for exact values.

Summary

  • Encode to JSON with JSONEncoder, then parse to [String: Any] with JSONSerialization
  • Create an Encodable extension with asDictionary() for reusable conversion
  • Custom CodingKeys control the dictionary key names (e.g., camelCase to snake_case)
  • Set dateEncodingStrategy on JSONEncoder to control how dates appear in the dictionary
  • To go from dictionary back to Codable, serialize with JSONSerialization then decode with JSONDecoder

Course illustration
Course illustration

All Rights Reserved.