Codable and JSON Parsing
Codable is a type alias for the combination of Encodable and Decodable, two protocols that let Swift automatically synthesize code to convert your types to and from external representations like JSON. When every stored property of a struct or class conforms to Codable itself (String, Int, Date, arrays, and nested Codable types all qualify), the Swift compiler generates the encoding and decoding logic for you at compile time — no reflection, no manual key-by-key parsing. This is the standard way iOS apps parse API responses, and it pairs naturally with JSONDecoder/JSONEncoder for JSON specifically, though the same protocols also work with property list and other encoders.
Cricket analogy: Just as a scorecard app auto-fills runs, wickets, and overs from a standard scoresheet format without a scorer re-typing each field, Codable auto-generates conversion code so a struct's stored properties map straight to JSON without manual parsing.
Automatic Synthesis
The simplest case requires no code at all beyond declaring conformance. If your struct's property names match the JSON keys exactly, Codable synthesis handles everything: JSONDecoder().decode(User.self, from: data) will populate a User value directly from matching JSON fields. This works because the compiler generates a synthesized CodingKeys enum whose cases mirror your stored properties, and the synthesized init(from:)/encode(to:) implementations use it internally.
Cricket analogy: Like a scorecard where the JSON field 'runs' maps directly to a struct property named 'runs' with no translation needed, JSONDecoder populates a User type straight from matching JSON keys without any custom mapping code.
struct User: Codable {
let id: Int
let name: String
let email: String
let signupDate: Date
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let user = try decoder.decode(User.self, from: jsonData)Custom Keys with CodingKeys
Real-world APIs frequently use snake_case keys while Swift convention is camelCase. Rather than writing custom init(from:)/encode(to:) by hand, you can either set decoder.keyDecodingStrategy = .convertFromSnakeCase, or define an explicit CodingKeys enum that maps each Swift property to its JSON key name. CodingKeys must conform to String, CodingKey, and any property you omit from it will not be encoded or decoded — which is also a convenient way to exclude a property from serialization entirely.
Cricket analogy: Like a scorecard feed that uses 'strike_rate' from an Indian broadcast API while your app's model uses strikeRate, you define a CodingKeys enum mapping strikeRate to 'strike_rate' instead of writing a full custom decoder by hand.
struct Article: Codable {
let id: Int
let title: String
let publishedAt: Date
enum CodingKeys: String, CodingKey {
case id
case title
case publishedAt = "published_at"
}
}Custom Decoding Logic
When the JSON shape doesn't map cleanly onto your model — nested objects you want flattened, fields that may arrive as either a string or a number, or values needing validation — you implement init(from decoder: Decoder) throws manually. You obtain a KeyedDecodingContainer via decoder.container(keyedBy: CodingKeys.self), then decode each field explicitly, optionally using decodeIfPresent for optional fields or catching per-field errors to supply defaults.
Cricket analogy: Like a scorecard where 'overs' might arrive as either '19.4' or 19.4 depending on the feed, you write a manual init(from decoder:) using KeyedDecodingContainer to decode it with fallback logic rather than trusting automatic synthesis.
struct Product: Decodable {
let id: Int
let priceCents: Int
enum CodingKeys: String, CodingKey {
case id, price
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
let priceString = try container.decode(String.self, forKey: .price)
guard let dollars = Double(priceString) else {
throw DecodingError.dataCorruptedError(
forKey: .price, in: container,
debugDescription: "Price is not a valid number string")
}
priceCents = Int(dollars * 100)
}
}Codable is not limited to JSON. The same struct can be encoded to a property list with PropertyListEncoder, making it easy to persist small model objects to disk or UserDefaults without writing format-specific code.
A single malformed field anywhere in a Codable struct's synthesized decoding will cause the entire decode to throw, even if the rest of the JSON is valid. For APIs with unreliable fields, prefer explicit decodeIfPresent with sensible fallbacks over letting synthesis fail the whole object.
Codable=Encodable & Decodable; conformance can be automatically synthesized when all properties are themselves Codable.JSONDecoder/JSONEncoderbridge Codable types to and from JSONData.CodingKeysmaps Swift property names to different JSON key names, or excludes properties from serialization.keyDecodingStrategy = .convertFromSnakeCasehandles snake_case-to-camelCase conversion without custom keys.- Manual
init(from:)implementations handle irregular JSON shapes, type coercion, or validation logic. - A single decoding failure anywhere in a type throws for the whole object unless handled with decodeIfPresent or try?.
Practice what you learned
1. What two protocols does Codable combine?
2. What is the purpose of a custom CodingKeys enum?
3. Which JSONDecoder property automatically converts snake_case JSON keys to camelCase Swift properties?
4. When must you write a custom init(from decoder:) implementation instead of relying on synthesis?
5. What happens if one field fails to decode in a Codable struct using default synthesized decoding?
Was this page helpful?
You May Also Like
Networking with URLSession
Understand how URLSession performs HTTP requests in Swift, including async/await APIs, request configuration, and handling responses safely.
SwiftData Basics
Learn how SwiftData lets you model, persist, and query app data using plain Swift types annotated with macros, replacing much of Core Data's boilerplate.
UserDefaults and Persistence
Learn when and how to use UserDefaults for lightweight app settings and small persisted values, and how it compares to other persistence options.
Unit Testing ViewModels in Swift
Learn how to write focused, reliable unit tests for SwiftUI ViewModels using XCTest or Swift Testing, including async code and dependency injection.