Hello everyone, I think I've discovered a bug in JSONDecoder
and I'd like to get a quick sanity-check on this, as well as hopefully some ideas to work around the issue.
When attempting to decode a Decodable
struct from JSON using JSONDecoder
, the decoder throws an error when it encounters a dictionary that is keyed by an enum
and somehow seems to think that the enum
is an Array<Any>
.
Here's a minimal reproducible example:
let jsonString = """
{
"variations": {
"plural": "tests",
"singular": "test"
}
}
"""
struct Json: Codable {
let variations: [VariationKind: String]
}
enum VariationKind: String, Codable, Hashable {
case plural = "plural"
case singular = "singular"
}
and then the actual decoding:
let json = try JSONDecoder().decode(
Json.self,
from: jsonString.data(using: .utf8)!
)
print(json)
The expected result would of course be the following:
Json(
variations: [
VariationKind.plural: "tests",
VariationKind.singular: "test"
]
)
But the actual result is an error:
Swift.DecodingError.typeMismatch(
Swift.Array<Any>,
Swift.DecodingError.Context(
codingPath: [
CodingKeys(stringValue: "variations", intValue: nil)
],
debugDescription: "Expected to decode Array<Any> but found a dictionary instead.",
underlyingError: nil
)
)
So basically, the JSONDecoder
tries to decode Swift.Array<Any>
but encounters a dictionary (duh), and I have no idea why this is happening. There are literally no arrays anywhere, neither in the actual JSON string, nor in any of the Codable
structs.
Curiously, if I change the dictionary from [VariationKind: String]
to [String: String]
, everything works perfectly.
So something about the enum seems to cause confusion in JSONDecoder
. I've tried to fix this by implementing Decodable
myself for VariationKind
and using a singleValueContainer
, but that causes exactly the same error.
Am I crazy, or is that a bug?
They explain here a subtle behaviour (to say the least) of JSONEncoder.
https://forums.swift.org/t/encoding-decoding-a-swift-dictionary-to-from-json/39989
A dictionary with Int or String key is encoded as dictionary.
But with other type key, it is encoded as an array. That seems to be the case with the enum.
I tested by catching error:
do {
let json = try JSONDecoder().decode(Json.self, from: jsonString.data(using: .utf8)!)
print("JSON", json)
} catch {
print("Error", error)
}
and got the same error message:
Error typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "variations", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
As you noted, if you replace the key type by String:
struct Json: Codable {
let variations: [String: String]
}
Then you get the expected result:
JSON Json(variations: ["plural": "tests", "singular": "test"])