Almost every app talks to a server, and almost every server speaks JSON: text made of objects, arrays, strings, numbers and booleans. An app has to turn that text into Swift values it can use, and turn its own data back into JSON to send or save.
Swift makes this remarkably easy with Codable. Declare that a struct is Codable, make its property names match the JSON keys, and Swift writes all the conversion code for you. JSONDecoder turns JSON data into your structs, and JSONEncoder turns structs into JSON. Because the result is a normal typed struct, the compiler then checks every use of the data.
Real JSON is rarely a perfect match for your Swift code, though. Keys may use snake_case, fields may be missing, dates come in different formats, and data is nested. In this lesson you'll decode and encode JSON, rename keys, handle optional fields and dates, and deal with decoding errors properly.
Decoding JSON
import Foundation
struct Product: Codable {
let id: Int
let name: String
let price: Double
}
let json = #"{"id": 7, "name": "Lamp", "price": 24.5}"#
let data = Data(json.utf8)
let product = try JSONDecoder().decode(Product.self, from: data)
print(product.name) // LampCodableis two protocols together:Decodable(from JSON) andEncodable(to JSON).- Swift generates the code automatically when every property is itself Codable, which includes String, Int, Double, Bool, Date, URL, arrays, dictionaries, optionals and your own Codable types.
decodethrows if the JSON doesn't match, so it's called withtry.#"..."#is a raw string, so the quotes inside need no escaping.
Encoding to JSON
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(product)
print(String(decoding: data, as: UTF8.self))encode produces Data, which you can send to a server, write to a file or store in UserDefaults. .prettyPrinted adds line breaks and indentation, and .sortedKeys gives a stable key order, which is handy for logs and tests.
Keys, optional fields and dates
When JSON keys don't match your property names, either set a strategy or provide CodingKeys:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // first_name -> firstName
decoder.dateDecodingStrategy = .iso8601 // "2026-03-01T10:00:00Z"
struct User: Codable {
let id: Int
let displayName: String
let bio: String? // missing or null is fine
enum CodingKeys: String, CodingKey {
case id
case displayName = "name" // JSON key "name"
case bio
}
}A property that's optional decodes as nil when the key is missing or null. A non-optional property that's missing makes decoding fail with an error that names the key.
Enums in JSON
A JSON field that only allows a few strings maps neatly onto a Swift enum. Give the enum a String raw type and add Codable, and Swift decodes it by raw value:
enum Status: String, Codable {
case active, suspended
}Now "status": "active" decodes to .active. If the server sends a value with no matching case, such as "banned", decoding fails with a DecodingError instead of silently storing something wrong. That's usually what you want, but it does mean a new value on the server can break an older app, so check what the API promises before choosing an enum.
Default values with a custom init(from:)
Sometimes a missing field should become a sensible default rather than nil. Write your own decoding initializer and use decodeIfPresent, which returns nil when the key is missing:
struct Account: Codable {
let id: Int
let status: Status
let tags: [String]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
status = try container.decode(Status.self, forKey: .status)
tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
}
}JSON with no tags key now gives an empty array. Swift still generates CodingKeys and the encoding code for you, so you only write the part that's different. Writing init(from:) inside the struct does remove its memberwise initializer, though, so add one back or move the decoder into an extension if you need both.
Swift JSON Syntax
Codable syntax
struct Item: Codable { let id: Int; let title: String; let tags: [String]? }
let item = try JSONDecoder().decode(Item.self, from: data)
let items = try JSONDecoder().decode([Item].self, from: data)
let out = try JSONEncoder().encode(item)
enum CodingKeys: String, CodingKey { case id, title = "item_title", tags }
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]Swift JSON Summary
- Codable = Decodable + Encodable; Swift writes the code when all properties are Codable.
- JSONDecoder().decode(Type.self, from: data) reads JSON and throws on mismatches.
- JSONEncoder().encode(value) writes JSON; outputFormatting controls its layout.
- CodingKeys and keyDecodingStrategy map JSON keys to property names.
- Optional properties tolerate missing fields; dateDecodingStrategy parses dates.
Swift JSON Examples
Each example shows the code, the output it produces and what each part does.
Decoding an API response
import Foundation
struct Order: Codable {
let orderId: Int
let customerName: String
let items: [Item]
let note: String?
struct Item: Codable {
let name: String
let quantity: Int
let unitPrice: Double
}
}
let json = """
{
"order_id": 1042,
"customer_name": "Ada",
"items": [
{"name": "Mug", "quantity": 2, "unit_price": 8.5},
{"name": "Lamp", "quantity": 1, "unit_price": 24.0}
]
}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let order = try decoder.decode(Order.self, from: Data(json.utf8))
let total = order.items.reduce(0) { $0 + Double($1.quantity) * $1.unitPrice }
print("Order \(order.orderId) for \(order.customerName)")
print("\(order.items.count) lines, total £\(total)")
print(order.note ?? "No note")Order 1042 for Ada
2 lines, total £41.0
No noteLine-by-line explanation
- Line 7note is optional, so its absence from the JSON is fine.
- Lines 9 to 13A nested Codable struct decodes the array of item objects.
- Line 28convertFromSnakeCase maps order_id to orderId and unit_price to unitPrice.
- Line 29decode throws if the JSON doesn't match the structs.
- Line 312 mugs at 8.5 plus 1 lamp at 24.0 gives 41.0.
Encoding with CodingKeys
import Foundation
struct Profile: Codable {
let id: Int
let displayName: String
let isPro: Bool
enum CodingKeys: String, CodingKey {
case id
case displayName = "name"
case isPro = "pro"
}
}
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(Profile(id: 3, displayName: "Grace", isPro: true))
print(String(decoding: data, as: UTF8.self)){
"id" : 3,
"name" : "Grace",
"pro" : true
}Line-by-line explanation
- Lines 8 to 12CodingKeys gives each property its JSON key; every property must be listed.
- Line 16prettyPrinted adds indentation and sortedKeys orders the keys alphabetically.
- Line 18Encoded output is Data, turned into a String here to print it.
Handling decoding errors and dates
import Foundation
struct Event: Codable {
let title: String
let starts: Date
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let samples = [
#"{"title": "Launch", "starts": "2026-03-01T10:00:00Z"}"#,
#"{"title": "Meetup"}"#
]
for json in samples {
do {
let event = try decoder.decode(Event.self, from: Data(json.utf8))
let year = Calendar(identifier: .gregorian).component(.year, from: event.starts)
print("\(event.title) in \(year)")
} catch DecodingError.keyNotFound(let key, _) {
print("Missing key: \(key.stringValue)")
} catch {
print("Other error: \(error)")
}
}Launch in 2026
Missing key: startsLine-by-line explanation
- Line 9iso8601 parses dates written like 2026-03-01T10:00:00Z.
- Line 13The second sample has no starts value, and starts isn't optional.
- Line 19Calendar reads parts of a Date, such as the year.
- Lines 21 to 22DecodingError cases describe exactly what went wrong, including the missing key.
Common Swift JSON Mistakes
Property names that don't match the JSON
struct User: Codable { let firstName: String } // JSON has "first_name": decoding failslet decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCaseUsing try! on server data
let user = try! JSONDecoder().decode(User.self, from: data) // any API change crashes the appdo {
let user = try JSONDecoder().decode(User.self, from: data)
show(user)
} catch {
showError("Couldn't read the response.")
}Non-optional fields the API sometimes leaves out
struct Post: Codable { let subtitle: String } // fails when subtitle is missingstruct Post: Codable { let subtitle: String? }Swift JSON in Real Projects
Codable in production apps
A typical networking function in an iOS app is only a few lines: let (data, _) = try await URLSession.shared.data(from: url) followed by try decoder.decode([Article].self, from: data). Apps also use Codable to save settings and drafts to disk, to cache responses for offline use, and with SwiftData and CloudKit. Shared decoders, configured once with the right key and date strategies, keep every request consistent.
Swift JSON FAQ
What is the difference between Codable, Encodable and Decodable?
Decodable means a type can be created from external data such as JSON, and Encodable means it can be turned into it. Codable is simply both together. Use only Decodable for API responses you never send back, if you want to make that clear.
How do I decode a JSON array in Swift?
Pass an array type to the decoder: try JSONDecoder().decode([Product].self, from: data). If the array sits inside an object, such as {"items": [...]}, make a wrapper struct with an items: [Product] property and decode that instead.
Why does my JSON decoding fail with keyNotFound?
A non-optional property has no matching key in the JSON. Check the spelling and case of the key, add a CodingKeys mapping or .convertFromSnakeCase if the names differ, or make the property optional if the server sometimes leaves it out.
How do I convert snake_case JSON keys to camelCase in Swift?
Set decoder.keyDecodingStrategy = .convertFromSnakeCase before decoding, and first_name maps to firstName automatically. For encoding, set encoder.keyEncodingStrategy = .convertToSnakeCase.
How do I print JSON from a Codable struct?
Encode it and turn the data into a string: let data = try JSONEncoder().encode(value), then print(String(decoding: data, as: UTF8.self)). Set outputFormatting to [.prettyPrinted, .sortedKeys] to make it readable.
Swift JSON Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the line that turns JSON data into a Book.
let book = try JSONDecoder().decode(, from: data)
Which two protocols make up Codable?
The JSON is {"name": "Ada"}. What happens when it's decoded into struct U: Codable { let name: String; let age: Int? }?
What does keyDecodingStrategy = .convertFromSnakeCase do?
If you write CodingKeys for a struct, what must it include?
Swift JSON Quiz
5 questions to check what you learned.
What does JSONEncoder produce?
- A String
- Data
- A Dictionary
- A URL
Why is decode called with try?
- It throws an error if the JSON doesn't match the type
- It's async
- It returns an optional
- It's required for all functions
How do you decode a JSON array of products?
- decode(Product.self, ...)
- decodeArray(Product.self, ...)
- decode([Product].self, ...)
- decode(Array.self, ...)
Which strategy parses dates like 2026-03-01T10:00:00Z?
- .secondsSince1970
- .iso8601
- .deferredToDate
- .millisecondsSince1970
What does .sortedKeys in outputFormatting do?
- Sorts array items
- Removes duplicate keys
- Compresses the JSON
- Writes object keys in alphabetical order, giving stable output