How do you read a DecodingError, and how do you decode tolerantly?
Predict first
What does this print?
struct Item: Decodable { let id: Int }
struct Box: Decodable { let items: [Item] }
do {
_ = try JSONDecoder().decode(Box.self, from: Data(#"{"items":[{"id":"x"}]}"#.utf8))
} catch DecodingError.typeMismatch(_, let ctx) {
print(ctx.codingPath.map(\.stringValue))
}
- ["items", "id"]
- ["items", "Index 0", "id"]
- ["id"]
Predict first
What does this print for a missing key?
struct Item: Decodable { let id: Int }
struct Box: Decodable { let items: [Item] }
do {
_ = try JSONDecoder().decode(Box.self, from: Data(#"{"items":[{}]}"#.utf8))
} catch DecodingError.keyNotFound(let key, let ctx) {
print(key.stringValue, ctx.codingPath.map(\.stringValue))
}
- id ["items", "Index 0"]
- id ["items", "Index 0", "id"]
Mental model. The coding path is a street address for the bug: building, floor, door.
Decoding failures throw a DecodingError with four cases: keyNotFound, typeMismatch, valueNotFound and dataCorrupted. Each carries a Context whose codingPath says exactly where it went wrong, for example ["items", "Index 0", "id"], plus a debugDescription. Log the path and the case, not the raw payload, which may contain personal data.
When a server is sloppy, make the model tolerant on purpose: decodeIfPresent for optional fields, defaults in a custom init(from:), and a wrapper that decodes an array element by element so one bad element is skipped instead of failing the whole list. Never wrap everything in try?: you lose the path that would have told you why.
The path walks the document: the items key, array element 0, then the id key.
For keyNotFound the path leads to the container that lacks the key, and the key is reported separately.
Common traps
try?everywhere. You turn a precise error into silence.- Logging the whole payload on failure. It can leak user data. Log the path.
They might follow up with
- How does the path show array positions? As keys named
Index 0,Index 1. - How do you test the unhappy path? Feed malformed fixtures and assert the case and the path.
Related questions
- 2.5.1 What does keyDecodingStrategy do, and when does convertFromSnakeCase not match?
- 2.5.5 When would you use JSONSerialization instead of Codable, and what does the untyped route cost?
← Which JSONEncoder and JSONDecoder options matter beyond keys and… When would you use JSONSerialization instead of Codable, and what… →