swiftlang / swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence
swift.org
Apache License 2.0
5.29k stars 1.13k forks source link

[SR-7404] JSONDecoder – singleValueContainer fails with Optional<T> values #4029

Open swift-ci opened 6 years ago

swift-ci commented 6 years ago
Previous ID SR-7404
Radar rdar://problem/39354522
Original Reporter swillits (JIRA User)
Type Bug
Environment Xcode 9.3 Swift 4.1
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 3 | |Component/s | Foundation | |Labels | Bug, Codable | |Assignee | None | |Priority | Medium | md5: d0185b472c3142092e89fd809ebaa821

Issue Description:

JSONEncoder can encode a nil single value Optional\<String> as JSON "null", but JSONDecoder does is unable to interpret the null correctly.

Consider:

struct Foo: Codable {

    struct Value<T: Codable>: Codable {
        var value: T

        init(default def: T) {
            value = def
        }

        init(from decoder: Decoder) throws {
            let container = try! decoder.singleValueContainer()
            value = try! container.decode(T.self)
        }

        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try! container.encode(value)
        }
    }   

    var test = Value<String?>(default: nil)
}

var f1 = Foo()
let data = try! JSONEncoder().encode(f1)
print(String(data: data, encoding: .utf8)!) // {"test":null}

let f2 = try! JSONDecoder().decode(Foo.self, from: data)

When the nil Optional\<String> value is encoded, it is encoded as a single-value null as expected.

{"test":null}

But when decoding, an error is thrown:

Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.valueNotFound(Swift.Optional<Swift.String>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "test", intValue: nil)], debugDescription: "Expected Optional<String> but found null value instead.", underlyingError: nil)): file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-902.0.48/src/swift/stdlib/public/core/ErrorType.swift, line 184

Note that when switching singleValueContainer to unkeyedContainer, there is no similar error, but the encoded json then looks like: {"test":[null]}

belkadan commented 6 years ago

@itaiferber?

itaiferber commented 6 years ago

I think we just need to have JSONEncoder.decode<T : Decodable>(_ type: T.Type) not expectNonNull as it does today. If the value contained is indeed null, the actual decoding of the inner value will fail for non-optional types.

What we do need to do is handle unboxing the type afterwards correctly. Since the return result here is T (in which T may be optional), we might need to differentiate between the result being nil because it failed, and the result being nil because nil is valid.

itaiferber commented 6 years ago

@swift-ci Create