Search code examples
jsonswiftdictionarydecodingjsondecoder

Swift JSON decoding dictionary fails


I have a structure as shown below:

struct ItemList: Decodable {
    var items: [UUID: Int]
}

Example JSON data I get is:

{
    "items": {
        "b4f8d2fa-941f-4f9a-a98c-060bbd468575": 418226428193,
        "81efa661-4845-491b-8bf4-06d5dff1d5f8": 417639857722
    }
}

Now, when I try to decode the above data, I get an interesting error. Clearly, I'm not decoding an array and clearly everything points at a dictionary.

try JSONDecoder().decode(ItemList.self, from: data)
// typeMismatch(
//     Swift.Array<Any>,
//     Swift.DecodingError.Context(
//         codingPath: [
//             CodingKeys(stringValue: "items", intValue: nil)
//         ],
//         debugDescription: "Expected to decode Array<Any> but found a dictionary instead.",
//         underlyingError: nil
//     )
// )

So I went experimenting and changed the [UUID: Int] to [String: Int], which does make this work, almost making me think the error is not array/dictionary related, but UUID/String related. So I also did the following test, which never fails.

let list = try JSONDecoder().decode(ItemList.self, from: data)
for (key, value) in list.items {
    // This will never print `nil`
    print(UUID(uuidString: key))
}

So my question is, why do I get this weird typeMismatch error when decoding, and why does it work when I change the UUID to a String, as it can clearly be properly decoded?


Solution

  • this article gives a good explanation about why this happens and what you can do about it. Short summary:

    • Swift encodes a dictionary as an array, where each value follows a key
    • There are a couple of approaches that you can use to overcome the problem:

    a) Bend to swift's way using the array-representation of a dict
    b) Use String or Int as your key-type
    c) Use a custom decoder
    d) Use the RawRepresentable-Protocol