Decoding a dictionary with a custom type (not String) as key

Hello everybody :slight_smile:

The following code builds and runs, and prints ["key": "value"], as expected:

typealias Dict = [String: String]

do {
    let decoder = JSONDecoder()
    let string = Data("""
    {
        "key": "value"
    }
    """.utf8)

    let decoded = try decoder.decode(Dict.self, from: string)
    print(decoded)
}

When I change the type of the key as follows:

struct String2: Decodable, Hashable {
    let value: String

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

typealias Dict = [String2: String]

do {
    let decoder = JSONDecoder()
    let string = Data("""
    {
        "key": "value"
    }
    """.utf8)

    let decoded = try decoder.decode(Dict.self, from: string)
    print(decoded)
}

I would expect it to behave similarly. The reality is that the code builds, but when running shows the following error:

Fatal error: Error raised at top level: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil)): file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/ErrorType.swift, line 200

Such code does not even reach the content of the init(from decoder: Decoder) throws function inside the String2 type.

Am I missing something? Any idea why this happens?

Thanks,
Andres

Dictionary has the following coding strategy:

  1. If Key is String or Int and nothing else, it uses keyed container which, for JSON, corresponds to

    { "key1": <value1>, "key2": <value2>, ... }
    
  2. Otherwise, it uses unkeyed container and encode it like this:

    [ <key1>, <value1>, <key2>, <value2>, ... ]
    

The error is raise because to data is an object, but you're expecting an array (because of rule 2), which it detects long before init(from:).

1 Like

I see. Thanks for the answer.

Is it unreasonable to expect the second piece of code to work as I mentioned?

It was discussed before a few times, here for one.

1 Like

See also the discussion in Bug or PEBKAC? (and my response in the thread for some background on why this is the case).

1 Like

Should be this one (I tried to link there, but couldn't find the post :stuck_out_tongue:).

(Thanks, fixed! Copy-pasta... :upside_down_face:)

1 Like
Terms of Service

Privacy Policy

Cookie Policy