Codable and JSON basicsswift-6.4/ios-26
Lesson 1 / 6
Unit 10 · Networking & Codable

Codable and JSON basics

Coming from
Note

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.

Tip

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.

Coming from Kotlin

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.

Decode a JSON object into a struct
Edit the code on the right, then run the hidden tests.

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.

Solution.swiftSwift Testing
ConsoleReady · runs in a sandboxed Swift container
Press Run to compile against the hidden test suite.