Swift Codable Patterns
Comprehensive guide to Codable protocol conformance for JSON and PropertyList encoding/decoding in Swift 6.x.
Quick Reference
Decision Tree: When to Use Each Approach
Has your type...
ββ All properties Codable? β Automatic synthesis (just add `: Codable`)
ββ Property names differ from JSON keys? β CodingKeys customization
ββ Needs to exclude properties? β CodingKeys customization
ββ Enum with associated values? β Check enum synthesis patterns
ββ Needs structural transformation? β Manual implementation + bridge types
ββ Needs data not in JSON? β DecodableWithConfiguration (iOS 15+)
ββ Complex nested JSON? β Manual implementation + nested containers
Common Triggers
| Error |
Solution |
| "Type 'X' does not conform to protocol 'Decodable'" |
Ensure all stored properties are Codable |
| "No value associated with key X" |
Check CodingKeys match JSON keys |
| "Expected to decode X but found Y instead" |
Type mismatch; check JSON structure or use bridge type |
| "keyNotFound" |
JSON missing expected key; make property optional or provide default |
| "Date parsing failed" |
Configure dateDecodingStrategy on decoder |
Part 1: Automatic Synthesis
Swift automatically synthesizes Codable conformance when all stored properties are Codable.
Struct Synthesis
struct User: Codable {
let id: UUID
var name: String
var membershipPoints: Int
}
Requirements:
- All stored properties must conform to Codable
- Properties use standard Swift types or other Codable types
- No custom initialization logic needed
Enum Synthesis Patterns
Pattern 1: Raw Value Enums
enum Direction: String, Codable {
case north, south, east, west
}
The raw value itself becomes the JSON representation.
Pattern 2: Enums Without Associated Values
enum Status: Codable {
case success
case failure
case pending
}
Each case becomes an object with the case name as the key and empty dictionary as value.
Pattern 3: Enums With Associated Values
enum APIResult: Codable {
case success(data: String, count: Int)
case error(code: Int, message: String)
}
Gotcha: Unlabeled associated values generate _0, _1 keys:
enum Command: Codable {
case store(String, Int)
}
Fix: Always label associated values for predictable JSON:
enum Command: Codable {
case store(key: String, value: Int)
}
When Synthesis Breaks
Automatic synthesis fails when:
- Computed properties - Only stored properties are encoded
- Non-Codable properties - Custom types without Codable conformance
- Property wrappers -
@Published, @State (except @AppStorage with Codable types)
- Class inheritance - Subclasses must implement
init(from:) manually
Part 2: CodingKeys Customization
Use CodingKeys enum to customize encoding/decoding without full manual implementation.
Renaming Keys
struct Article: Codable {
let url: URL
let title: String
let body: String
enum CodingKeys: String, CodingKey {
case url = "source_link"
case title = "content_name"
case body
}
}
Excluding Properties
Omit properties from CodingKeys to exclude them from encoding/decoding:
struct NoteCollection: Codable {
let name: String
let notes: [Note]
var localDrafts: [Note] = []
enum CodingKeys: CodingKey {
case name
case notes
}
}
Rule: Excluded properties require default values or you must implement init(from:) manually.
Snake Case Conversion
For consistent snake_case β camelCase conversion:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Enum Associated Value Keys
Customize keys for enum associated values using {CaseName}CodingKeys:
enum Command: Codable {
case store(key: String, value: Int)
case delete(key: String)
enum StoreCodingKeys: String, CodingKey {
case key = "identifier"
case value = "data"
}
enum DeleteCodingKeys: String, CodingKey {
case key = "identifier"
}
}
Pattern: {CaseName}CodingKeys with capitalized case name.
Part 3: Manual Implementation
For structural differences between JSON and Swift models, implement init(from:) and encode(to:).
Container Types
| Container |
When to Use |
| Keyed |
Dictionary-like data with string keys |
| Unkeyed |
Array-like sequential data |
| Single-value |
Wrapper types that encode as a single value |
| Nested |
Hierarchical JSON structures |
Nested Containers Example
Flatten hierarchical JSON:
struct Coordinate {
var latitude: Double
var longitude: Double
var elevation: Double
enum CodingKeys: String, CodingKey {
case latitude, longitude, additionalInfo
}
enum AdditionalInfoKeys: String, CodingKey {
case elevation
}
}
extension Coordinate: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
latitude = try values.decode(Double.self, forKey: .latitude)
longitude = try values.decode(Double.self, forKey: .longitude)
let additionalInfo = try values.