Skip to Content
Course content

39: Custom Coding Keys and Strategies

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a project recently where I had to integrate a legacy API from a partner company. The API documentation was sparse, and the JSON they were sending back was... well, it was a nightmare. It used a mix of snake_case and these weird, abbreviated keys that looked like they were designed for a 1980s mainframe.

The "Why is this nil?" moment

I started off simple. I had a JSON response that looked like this:

{
    "user_id": 101,
    "full_name": "Jane Doe",
    "account_status_active": true
}

Naturally, I wrote a Swift struct to match it, following our usual camelCase conventions:

struct User: Codable {
    let userId: Int
    let fullName: String
    let accountStatusActive: Bool
}

I ran it through a JSONDecoder, and—surprise, surprise—it crashed with a keyNotFound error. The decoder was looking for a key called "userId" in the JSON, but the JSON only had "user_id". Swift doesn't just "know" that these are the same thing. It's looking for an exact string match.

Mapping the chaos

Now, I could just name my Swift properties user_id, but that feels wrong. It violates every style guide we have and makes the rest of the codebase look messy. Instead, I remembered we can tell Swift exactly how to map the JSON keys to our properties using a special enum called CodingKeys.

I added this inside my struct:

struct User: Codable {
    let userId: Int
    let fullName: String
    let accountStatusActive: Bool

    enum CodingKeys: String, CodingKey {
        case userId = "user_id"
        case fullName = "full_name"
        case accountStatusActive = "account_status_active"
    }
}

I ran the decoder again. Success! This works because Codable looks for a CodingKeys enum first. If it finds one, it uses those string values to find the data in the JSON, but assigns it to the property name we defined. It's a bit of a chore to write, but it keeps the Swift side of things clean.

Automating the boring stuff

As I kept digging into the API, I realized there were dozens of these models. Writing a CodingKeys enum for every single struct is a waste of my afternoon. I started wondering: is there a way to just tell the decoder "hey, everything is snake_case, just handle it"?

It turns out there is. I stripped the CodingKeys enum back out of my struct and tried this instead:

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let user = try decoder.decode(User.self, from: jsonData)

This is a lifesaver. .convertFromSnakeCase automatically transforms user_id to userId during the decoding process. No manual mapping required. If the API is consistent, this is always the way to go.

When the automation fails

But here is where it got tricky. The API designers decided to throw a curveball. One of the keys in the response was usr_id_internal_v1. I wanted that to be mapped to a property called internalId.

If I use .convertFromSnakeCase, Swift will try to turn that into usrIdInternalV1. That's still ugly. I tried adding a CodingKeys enum back in while keeping the keyDecodingStrategy active, but I noticed something weird: the CodingKeys mapping was being ignored or conflicting.

I realized that when you use a custom CodingKeys enum, you are taking full manual control. The keyDecodingStrategy is essentially a "global" shortcut, but the CodingKeys enum is a "specific" instruction. If I want a specific key to be totally different (not just snake-to-camel), I have to list all the keys in the enum, and I should probably remove the keyDecodingStrategy to avoid confusion.

So, my final version for that weird model looked like this:

struct UserProfile: Codable {
    let userId: Int
    let internalId: String

    enum CodingKeys: String, CodingKey {
        case userId = "user_id" // Still need this because we aren't using the global strategy
        case internalId = "usr_id_internal_v1" // The "weird" one
    }
}

The takeaway? Use .convertFromSnakeCase for consistency across the board, but the second you encounter a key that doesn't follow a predictable pattern, switch to a manual CodingKeys enum for that specific type.




📋 Practical Task

Refactoring the Legacy Product Feed

You are integrating a product feed from an old warehouse system. The JSON looks like this:

{
    "prod_id": "SKU-9928",
    "prod_name": "Ergonomic Keyboard",
    "wh_loc_code": "ZONE-B4"
}

Your goal is to create a Swift struct named Product that maps these keys to the following camelCase properties:

  • id (mapped from prod_id)
  • name (mapped from prod_name)
  • warehouseLocation (mapped from wh_loc_code)

Challenge: Because wh_loc_code doesn't map cleanly to warehouseLocation via a simple snake_case conversion, you must implement this using a custom CodingKeys enum rather than a keyDecodingStrategy.

Write the Product struct and the CodingKeys implementation to make this work.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.