Codable & JSON
Reviewed & published by Brayan K
Convert between Swift values and JSON with almost no boilerplate. Mark a type Codable, then use JSONDecoder to read JSON and JSONEncoder to write it — with CodingKeys for when the names don't match.
Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • What Codable, Encodable, and Decodable are
- • Decode JSON into a struct with JSONDecoder
- • Encode a struct to JSON with JSONEncoder
- • Map mismatched key names with CodingKeys
- • Handle snake_case with .convertFromSnakeCase
- • Decode optional/missing fields safely
1️⃣ Decoding JSON into a Struct
Describe the JSON's shape with a Codable struct, then let JSONDecoder fill it in. Every property must match a JSON key with a compatible type.
import Foundation
// Conforming to Codable lets Swift map JSON <-> this struct for free.
// Each property name must match a JSON key, with the matching type.
struct User: Codable {
let id: Int
let name: String
let isActive: Bool
}
let jsonString = """
{ "id": 7, "name": "Ada Lovelace", "isActive": true }
"""
let data = Data(jsonString.utf8)
// JSONDecoder reads the bytes and builds a User. Pass the TYPE (User.self).
let user = try JSONDecoder().decode(User.self, from: data)
print("\(user.name) — active: \(user.isActive)") // Ada Lovelace — active: true2️⃣ Encoding a Struct to JSON
Going the other way, JSONEncoder turns a Swift value into JSON Data. Set outputFormatting = .prettyPrinted for readable output.
import Foundation
struct Product: Codable {
let name: String
let price: Double
}
let item = Product(name: "Pen", price: 1.5)
// JSONEncoder turns a Swift value INTO JSON data (the reverse of decoding).
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // readable, indented output
let data = try encoder.encode(item)
print(String(data: data, encoding: .utf8)!)
// {
// "name" : "Pen",
// "price" : 1.5
// }3️⃣ CodingKeys and snake_case
When JSON keys don't match your property names, add a CodingKeys enum (String, CodingKey) to map them. For whole-model snake_case, a single .convertFromSnakeCase strategy does it automatically.
import Foundation
// When JSON keys differ from your Swift names, add a CodingKeys enum.
// Here the API uses snake_case but we want clean camelCase properties.
struct Person: Codable {
let firstName: String
let lastName: String
// Map each property to its JSON key. Conforms to String, CodingKey.
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
}
}
let jsonString = """
{ "first_name": "Ada", "last_name": "Lovelace" }
"""
let data = Data(jsonString.utf8)
let p = try JSONDecoder().decode(Person.self, from: data)
print("\(p.firstName) \(p.lastName)") // Ada Lovelace
// Shortcut for whole-model snake_case (no CodingKeys needed):
struct Tag: Codable { let tagName: String }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let tag = try decoder.decode(Tag.self, from: Data(#"{"tag_name":"swift"}"#.utf8))
print(tag.tagName) // swiftYour turn. Fill in the protocol and the decode method.
// 🎯 YOUR TURN — fill in each ___ then run it on SwiftFiddle.
import Foundation
// 1) Make the model match the JSON. It must conform to the protocol
// that enables BOTH encoding and decoding.
struct Book: ___ { // 👉 the typealias for Encodable & Decodable
let title: String
let pages: Int
}
let jsonString = """
{ "title": "Swift Basics", "pages": 120 }
"""
let data = Data(jsonString.utf8)
// 2) Decode the bytes into a Book. Pass the TYPE, not an instance.
let book = try JSONDecoder().___(Book.self, from: data) // 👉 the decode method
print("\(book.title): \(book.pages) pages")
// ✅ Expected output:
// Swift Basics: 120 pagesPro Tips
- 💡 Mirror the JSON exactly. Match every key name and type, or decoding throws.
- 💡 Use Optionals for "maybe missing" fields so an absent key becomes nil instead of an error.
- 💡 Prefer .convertFromSnakeCase over hand-writing CodingKeys when the only difference is casing.
- 💡 Conform to just Decodable if you only ever read JSON — you don't always need both directions.
Common Errors (and the fix)
- "keyNotFound": a property has no matching JSON key. Fix the name, add CodingKeys, or make it Optional.
- "typeMismatch": the JSON value's type differs from your property. Match the real type (e.g. the JSON has a string, not an Int).
- "dataCorrupted" / invalid JSON: the input isn't valid JSON. Check the source bytes.
- Forgot import Foundation: JSONDecoder/Data live in Foundation — import it.
📋 Quick Reference
| Task | Code |
|---|---|
| Enable JSON mapping | struct T: Codable { … } |
| Decode | JSONDecoder().decode(T.self, from: data) |
| Encode | JSONEncoder().encode(value) |
| Map key names | enum CodingKeys: String, CodingKey |
| snake_case | decoder.keyDecodingStrategy = .convertFromSnakeCase |
| Pretty JSON | encoder.outputFormatting = .prettyPrinted |
Mini-Challenge: Round-Trip a Model
No blanks this time — just a brief and an outline. Encode a value to JSON and decode it back to prove both directions work.
// 🎯 MINI-CHALLENGE: round-trip a model
// 1. Make a Codable struct Movie with: title (String), year (Int),
// rating (Double).
// 2. Create a Movie value of your choice.
// 3. ENCODE it to JSON data with JSONEncoder, then print the JSON string.
// 4. DECODE that data back into a Movie and print its title.
// (This proves the round-trip works both ways.)
//
// ✅ Expected: the printed JSON shows your fields, then the title prints.
// your code here
Practice quiz
What is Codable in Swift?
- A networking library
- A UI component
- A type alias for Encodable & Decodable, enabling JSON conversion
- A kind of optional
Answer: A type alias for Encodable & Decodable, enabling JSON conversion. Codable is a typealias combining Encodable and Decodable, so a Codable type can be both encoded and decoded.
Which protocol lets a type be turned INTO JSON (or other data)?
- Encodable
- Decodable
- Hashable
- Identifiable
Answer: Encodable. Encodable is the protocol for encoding a Swift value out to a format like JSON.
Which protocol lets JSON be turned INTO a Swift value?
- Encodable
- Comparable
- Equatable
- Decodable
Answer: Decodable. Decodable is the protocol for decoding data into a Swift value.
Which type encodes a Swift value to JSON data?
- JSONDecoder
- JSONEncoder
- JSONSerializer
- Data
Answer: JSONEncoder. JSONEncoder().encode(value) produces JSON Data from an Encodable value.
Which type decodes JSON data into a Swift value?
- JSONDecoder
- JSONEncoder
- JSONParser
- Codable
Answer: JSONDecoder. JSONDecoder().decode(T.self, from: data) builds a value of type T from JSON Data.
What is the purpose of a CodingKeys enum in a Codable type?
- To list errors
- To sort properties
- To map Swift property names to differently-named JSON keys
- To make properties optional
Answer: To map Swift property names to differently-named JSON keys. CodingKeys maps each property to its JSON key, useful when names differ (e.g. JSON first_name vs Swift firstName).
What must CodingKeys conform to?
- Int
- String, CodingKey (typically String-backed and CodingKey)
- Decodable only
- Any
Answer: String, CodingKey (typically String-backed and CodingKey). A CodingKeys enum is usually 'String, CodingKey', mapping property names to string keys.
How can you decode snake_case JSON keys into camelCase properties without writing CodingKeys?
- It is impossible
- Use JSONEncoder
- Mark properties weak
- Set decoder.keyDecodingStrategy = .convertFromSnakeCase
Answer: Set decoder.keyDecodingStrategy = .convertFromSnakeCase. .convertFromSnakeCase automatically maps first_name to firstName for the whole response.
If a JSON field might be absent, how should the matching Swift property be declared?
- As an Int
- As an Optional (e.g. String?) so a missing key decodes to nil
- As private
- As a constant
Answer: As an Optional (e.g. String?) so a missing key decodes to nil. Optional properties decode to nil when their key is missing, avoiding a keyNotFound error.
What does JSONEncoder's outputFormatting = .prettyPrinted do?
- Compresses the JSON
- Encrypts the JSON
- Produces human-readable, indented JSON
- Sorts keys alphabetically only
Answer: Produces human-readable, indented JSON. .prettyPrinted formats the encoded JSON with indentation and line breaks for readability.
Continue this course
- Previous: Concurrency: async/await
- Next: The Result Type — Model success and failure explicitly with Result<Success, Failure>
Frequently asked questions
What is Codable and how does it relate to Encodable and Decodable?
Codable is simply a type alias for the combination Encodable & Decodable. Encodable means a type can be turned into an external format like JSON; Decodable means a type can be created from such a format. When you write 'struct User: Codable', you get both directions for free, so you can encode a User to JSON and decode JSON back into a User. If you only need one direction, you can conform to just Encodable or just Decodable.
How do JSONEncoder and JSONDecoder work?
JSONEncoder().encode(value) takes an Encodable value and produces JSON as Data (raw bytes). JSONDecoder().decode(T.self, from: data) does the reverse: it reads JSON Data and builds a value of the type you pass as T.self. Both are part of Foundation. For decoding, each property in your type must line up with a JSON key of a compatible type — Swift synthesizes the mapping automatically for Codable types.
What are CodingKeys and when do I need them?
CodingKeys is a nested enum, usually declared as 'String, CodingKey', that maps each Swift property to the key used in the JSON. You need it when the JSON key names don't match your Swift property names — for example JSON 'first_name' versus Swift 'firstName' — or when you want to rename or omit a field. If all keys already match your property names, Swift generates the CodingKeys for you and you don't have to write one.
How do I handle snake_case JSON without writing CodingKeys for every property?
Set the decoder's keyDecodingStrategy to .convertFromSnakeCase before decoding: decoder.keyDecodingStrategy = .convertFromSnakeCase. This automatically converts keys like first_name into firstName for the entire response, so your properties can stay in idiomatic camelCase. There's a matching .convertToSnakeCase strategy on JSONEncoder for the reverse direction.
What happens if a JSON field is missing or has the wrong type?
Decoding throws a DecodingError. A missing key throws keyNotFound, and a type mismatch (for example a string where you expected an Int) throws typeMismatch. To tolerate a field that may be absent, declare the matching property as an Optional (like String?), which decodes to nil when the key is missing instead of throwing. For type mismatches, make sure your property's type mirrors the real JSON value.