Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
39: Custom Coding Keys and Strategies
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 fromprod_id)name(mapped fromprod_name)warehouseLocation(mapped fromwh_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.
There are no comments for now.