Blog / November 18, 2024 / 4 mins read / By Mahi Garg

Codable in Swift

Swift’s Codable protocol provides a powerful and convenient way to encode and decode data. It combines the capabilities of both Encodable and Decodable protocols, allowing seamless conversion between data formats (like JSON) and Swift objects.

This blog explores how Codable works, its features, and how to use it effectively, with examples for better understanding.

What is Codable?

Codable is a type alias for both Encodable and Decodable. When a type conforms to Codable, it can:

  • Encode itself to a data format (e.g., JSON).
  • Decode itself from a data format.
Typealias Definition:
typealias Codable = Encodable & Decodable

Why Use Codable?

  • Seamless Data Conversion: Easily convert between Swift objects and external representations (e.g., JSON, Property Lists).
  • Minimal Boilerplate: Swift generates most of the encoding/decoding logic automatically.
  • Error Handling: Provides strong type safety and clear error messages during encoding/decoding.

How to use Codable

Example 1: Encoding and Decoding a Simple Object

Consider a simple User struct:

import Foundation

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

let user = User(id: 1, name: "Mahi Garg", email: "mahi.garg@example.com")

Encoding to JSON

let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(user), 
   let jsonString = String(data: jsonData, encoding: .utf8) {
    print("JSON String: \(jsonString)")
}

Output:

{"id":1,"name":"Mahi Garg","email":"mahi.garg@example.com"}

Decoding from JSON

let json = """
{
    "id": 1,
    "name": "Mahi Garg",
    "email": "mahi.garg@example.com"
}
""".data(using: .utf8)!


let decoder = JSONDecoder()
if let decodedUser = try? decoder.decode(User.self, from: json) {
    print("Decoded User: \(decodedUser)")
}

Output:

Decoded User: User(id: 1, name: "Mahi Garg", email: "mahi.garg@example.com")
Example 2: Nested Data with Codable

Consider a Post object with nested user data:

struct Post: Codable {
    let title: String
    let content: String
    let author: User
}

let post = Post(
    title: "Swift Codable Guide",
    content: "Learning Codable is fun and useful!",
    author: User(id: 1, name: "Mahi Garg", email: "mahi.garg@example.com")
)

Encoding Nested Objects

if let postData = try? encoder.encode(post), 
   let postJSON = String(data: postData, encoding: .utf8) {
    print("Post JSON: \(postJSON)")
}

Output:

{
    "title": "Swift Codable Guide",
    "content": "Learning Codable is fun and useful!",
    "author": {
        "id": 1,
        "name": "Mahi Garg",
        "email": "mahi.garg@example.com"
    }
}

Decoding Nested Objects

let postJson = """
{
    "title": "Swift Codable Guide",
    "content": "Learning Codable is fun and useful!",
    "author": {
        "id": 1,
        "name": "Mahi Garg",
        "email": "mahi.garg@example.com"
    }
}
""".data(using: .utf8)!

if let decodedPost = try? decoder.decode(Post.self, from: postJson) {
    print("Decoded Post: \(decodedPost)")
}

Output:

Decoded Post: Post(title: "Swift Codable Guide", content: "Learning Codable is fun and useful!", author: User(id: 1, name: "Mahi Garg", email: "mahi.garg@example.com"))

Customizing Encoding and Decoding

Swift provides default implementations for Codable, but you can customize the behavior using the CodingKeys enum.

Example 3: Custom Key Mapping
struct CustomUser: Codable {
    let id: Int
    let fullName: String
    let emailAddress: String
    
    enum CodingKeys: String, CodingKey {
        case id
        case fullName = "name"
        case emailAddress = "email"
    }
}

let customUser = CustomUser(id: 2, fullName: "Mahi Garg", emailAddress: "mahi.garg@example.com")

if let customUserData = try? encoder.encode(customUser), 
   let customUserJSON = String(data: customUserData, encoding: .utf8) {
    print("Custom User JSON: \(customUserJSON)")
}

Output:

{"id":2,"name":"Mahi Garg","email":"mahi.garg@example.com"}

Handling Arrays with Codable

Example 4: Encoding and Decoding an Array
let users = [
    User(id: 1, name: "Mahi Garg", email: "mahi.garg@example.com"),
    User(id: 2, name: "John Doe", email: "john.doe@example.com")
]

if let usersData = try? encoder.encode(users),
   let usersJSON = String(data: usersData, encoding: .utf8) {
    print("Users JSON: \(usersJSON)")
}

Output:

[
    {"id":1,"name":"Mahi Garg","email":"mahi.garg@example.com"},
    {"id":2,"name":"John Doe","email":"john.doe@example.com"}
]

Error Handling in Codable

Example 5: Decoding with Errors
let invalidJson = """
{
    "id": "notAnInt",
    "name": "Mahi Garg",
    "email": "mahi.garg@example.com"
}
""".data(using: .utf8)!

do {
    let _ = try decoder.decode(User.self, from: invalidJson)
} catch {
    print("Decoding Error: \(error)")
}

Output:

Decoding Error: typeMismatch(Swift.Int, Swift.DecodingError.Context(...))

When to Use Codable

  • Working with APIs: Easily parse JSON data fetched from APIs or encode data to send.
  • Persistence: Store data locally in formats like JSON or Property List.
  • Modeling Complex Data: Handle nested and hierarchical data structures with ease.

Conclusion

The Codable protocol is a powerful feature in Swift, simplifying data encoding and decoding while ensuring type safety and reducing boilerplate code. Whether you’re working with simple objects, nested data, or custom key mappings, Codable has you covered. Understanding its capabilities and applications will make your Swift development process smoother and more efficient.

Use Codable to make your code cleaner, safer, and easier to maintain!

Comments