Codable and JSON basics
Unit 10 · Networking & Codable. This is the foundation for every network call in the unit — you fetch bytes, then turn them into types. This lesson is the "turn them into types" half.
Most apps talk to a server, and servers speak JSON. Swift's answer is Codable: you declare
the shape of the data as a struct, and the compiler writes the parser for you. No manual key
lookups, no casting Any, no third-party library. If the types line up, decoding is a single
call.
What Codable is
Codable is a type alias for two protocols:
typealias Codable = Encodable & Decodable
Decodable— the type can be created from an external format (JSON in).Encodable— the type can be written to an external format (JSON out).Codable— both.
When every stored property of a type is itself Codable, the compiler synthesizes the
conformance: it generates the decoding and encoding code from the property list. Int,
String, Double, Bool, Date, URL, arrays, dictionaries, and optionals of those are
all Codable already, so most model structs get it for free.
struct User: Codable { let id: Int let name: String }
That's the whole declaration. User is now serializable in both directions.
Decoding JSON into a struct
JSONDecoder turns Data into a value:
import Foundation let json = #"{"id":1,"name":"Ada"}"# let data = Data(json.utf8) let user = try JSONDecoder().decode(User.self, from: data) print(user.name) // Ada
Three things to note. You pass the type (User.self) so the decoder knows what to build.
It reads Data, not String, so Data(json.utf8) bridges the two — a non-optional,
allocation-free way to get UTF-8 bytes. And decode throws: a missing key or a wrong type
raises a DecodingError rather than returning a broken value.
Encoding a value back to JSON
The reverse uses JSONEncoder:
let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys // stable, readable output let out = try encoder.encode(user) print(String(decoding: out, as: UTF8.self)) // {"id":1,"name":"Ada"}
encode also returns Data; String(decoding:as:) is the non-optional inverse of
Data(_.utf8). You rarely encode in a read-only client, but you will the moment you POST.
Prefer Data(json.utf8) over json.data(using: .utf8). The latter returns an
Optional<Data> (UTF-8 encoding technically can fail for some encodings), forcing an unwrap.
Data(someString.utf8) can't fail and reads cleaner. Same for the way back: use
String(decoding: data, as: UTF8.self), which substitutes replacement characters instead of
returning nil.
This is Kotlin's kotlinx.serialization with @Serializable, but built into the standard
library rather than a plugin — no Json.decodeFromString<User>(text) compiler plugin to add.
Declaring the struct is the whole setup; conformance synthesis is a language feature.
Your turn
Decode a JSON object into a struct with a single call.
Knowledge check
Q: You declared struct User: Codable and wrote no init(from:). Where did the decoding
code come from?
The compiler synthesized it. When every stored property is Codable, Swift generates the
init(from:) and encode(to:) automatically from the property list.
Q: JSONDecoder().decode wants Data, but you have a String. What's the idiomatic
bridge?
Data(myString.utf8) — a non-optional conversion to UTF-8 bytes. Avoid
myString.data(using: .utf8), which returns an optional you'd have to unwrap.