Skip to content

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

Open
@swift-ci

Description

@swift-ci
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]}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions