When Decodable
and other archiving protocols were introduced there was a nasty oversight that persists to this day. Some nested dictionaries will encode in a weird non-convenient array format and also require the same format to decode correctly (natural nested dictionaries fail to decode with custom keys).
In the next example json_2
represents the JSON with the mentioned strange format (if you'd encode SomeType
it probably will look like this).
enum Key : String, CodingKey, Decodable {
case a
}
struct Inner : Decodable {
var test: String
}
struct SomeType<T> : Decodable where T : Decodable {
enum CodingKey : String, Swift.CodingKey {
case something
}
let storage: T
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKey.self)
storage = try container.decode(T.self, forKey: .something)
}
}
func decode<T>(_ value: String, _: T.Type) where T : Decodable {
let decoder = JSONDecoder()
guard
let data = value.data(using: .utf8)
else { return }
do {
let test = try decoder.decode(SomeType<T>.self, from: data)
print(test)
} catch {
print(error)
}
}
let json_1: String = """
{
"something": {
"a": {
"0001": {
"test": "Hello"
},
"0002": {
"test": "Swift"
}
}
}
}
"""
let json_2: String = """
{
"something": [
"a", {
"0001": {
"test": "Hello"
},
"0002": {
"test": "Swift"
}
}
]
}
"""
//typeMismatch(
// Swift.Array<Any>,
// Swift.DecodingError.Context(
// codingPath: [CodingKey(stringValue: "something", intValue: nil)],
// debugDescription:
// "Expected to decode Array<Any> but found a dictionary instead.",
// underlyingError: nil
// )
//)
decode(json_1, [Key: [String: Inner]].self)
// The following works
decode(json_1, [String: [String: Inner]].self)
//SomeType(
// storage: [
// Key(stringValue: "a", intValue: nil): [
// "0002": Inner(test: "Swift"),
// "0001": Inner(test: "Hello")
// ]
// ]
//)
decode(json_2, [Key: [String: Inner]].self)
To fix the issue in my codebase I had to create a wrapper type over dictionaries to manually fix the encoding and decoding issue.
struct DictionaryCoder<Key, Value> where Key : Hashable {
let dictionary: [Key: Value]
init(_ dictionary: [Key: Value]) {
self.dictionary = dictionary
}
}
extension DictionaryCoder : Decodable where Key: CodingKey, Value: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
let values = try container.allKeys.map {
($0, try container.decode(Value.self, forKey: $0))
}
dictionary = Dictionary(uniqueKeysWithValues: values)
}
}
extension DictionaryCoder : Encodable where Key: CodingKey, Value: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Key.self)
try dictionary.forEach {
try container.encode($0.value, forKey: $0.key)
}
}
}
decode(json_1, DictionaryCoder<Key, [String: Inner]>.self)
However I would wish for a general solution.