The synthesized implementation for `Decodable` doesn't interact with manual implementations as I would expect

I was surprised to see that the codingPath contained inside of DecodingError is not populated with the full chain of coding keys when the error is thrown manually.

For example, the following code:

func test_nestedDecodingError() {
    try! JSONDecoder()
        .decode(
            Foo.self,
            from: "{\"bar\": {\"bool\": true}}".utf8Data
        )
}
struct Foo: Decodable {
    var bar: Bar
}

struct Bar: Decodable {
    var bool: Bool
    
    init(from decoder: any Decoder) throws {
        throw
            DecodingError.keyNotFound(
                CodingKeys.someKey,
                DecodingError.Context(
                    codingPath: [CodingKeys.someKey],
                    debugDescription: "abc"
                )
            )
    }
    
    enum CodingKeys: CodingKey {
        case someKey
    }
}

yields the following error:

Swift.DecodingError.keyNotFound(
    CodingKeys(stringValue: "someKey", intValue: nil),
    Swift.DecodingError.Context(
        codingPath: [CodingKeys(stringValue: "someKey", intValue: nil)],
        debugDescription: "abc",
        underlyingError: nil
    )
)

Why is the codingPath not the following?

[
    CodingKeys(stringValue: "bar", intValue: nil),
    CodingKeys(stringValue: "someKey", intValue: nil)
]
1 Like

JSONEncoder/JSONDecoder (and other encoders and decoders) don't intercept-and-rethrow errors you throw, at least partially for performance, and partially because you could want to construct a custom codingPath to report that doesn't directly match the stack, for a variety of reasons; so, what you throw is what you get.

DecodingError does offer

as conveniences, which automatically construct the appropriate codingPath for you using the container's codingPath, though unfortunately not for keyNotFound or the other error variants (since those are usually just thrown automatically when you attempt to access a non-existent key).

Theoretically, DecodingError could offer similar methods for the other error types to do the same thing.

2 Likes

Are there any internal plans for doing that or we should better write a pitch?
I fall into the same issue sometimes. Appropriate errors are substantial for logging of nonfatal errors and network requests tracing.

I'm not aware of any plans to add new conveniences (not that I would have internal knowledge), but it seems like a straightforward pitch to me :+1:t3:

@jeremyabannister do you have time and motivation to collaborate for making a detailed pitch?

I do have general interest in collaborating on a pitch to improve Swift in some way, but my motivation is not very strong on this particular one at the moment, since so far it doesn't look like it will help me with what I'm working on right now, and I haven't tried to imagine other situations where I would need it.

Like I said though, I may be interested in collaborating on other pitches in the future though, so feel free to reach out.

1 Like

I now realize that Decoder provides a codingPath property which solves my needs, and I don't think the convenience methods you mentioned fit my use case. Thanks though for indirectly leading me to what I needed!