2

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?

Bram
  • 2,718
  • 1
  • 22
  • 43
  • 1
    This seems to be an [unresolved bug](https://bugs.swift.org/plugins/servlet/mobile#issue/SR-9023) and also one might argue about the value of using externally created uuid values. Are they guaranteed to be unique in your device, I am unsure about that – Joakim Danielson Apr 14 '21 at 08:15
  • 1
    Seems related: https://swiftsenpai.com/swift/decode-dynamic-keys-json/ – Tomalak Apr 14 '21 at 08:25
  • 1
    In my opinion this is not a bug. The fact that a key in dictionary is `Decodable` doesn't make much difference because JSON only supports `String` as dictionary key. However, `Decodable` cannot enforce that the key type can decode a `String`. Therefore they had a choice - try to decode a `String` into the given type and throw an exception if that's not possible, or, use a different way to store data when a non-String key is found. The first possibility would be problematic when encoding, so they went with the second one. – Sulthan Apr 14 '21 at 08:27

1 Answers1

2

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

Schottky
  • 1,549
  • 1
  • 4
  • 19