For complicated reasons I find myself working against the grain of Codable: when decoding a json object I want to preserve the sub-object under the key extra "just as json", stored as a [String: Any] dictionary, but [String: Any] is (of course) not Decodable. Is there any way to do this?
The first answer is of course "don't". To which my response is "I need to": I need to make two Codable passes over the data, in which the first decodes a list of heterogeneous objects (which each have a key name) while the second pass uses a dictionary keyed on those name values and is properly type-safe. The first pass cannot be type-safe because it's operating on a heterogenous list, but it needs to preserve all the data that the second pass will work with. Thankfully all the heterogenous data is hidden under that extra key, but still I don't see how to do it.
(There will very likely be a followup question about _en_coding the same things, so if you happen to have insight feel free to mention it.)
You can create a Custom Dictionary Decodable which decodes and stores the values in an Dictionary and then use this type for the key in which you want to store raw dictionary.
Here is the Decodable:
struct DictionaryDecodable: Decodable {
let dictionary : [String: Any]
private struct Key : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let con = try decoder.container(keyedBy: Key.self)
var dict = [String: Any]()
for key in con.allKeys {
if let value = try? con.decode(String.self, forKey:key) {
dict[key.stringValue] = value
} else if let value = try? con.decode(Int.self, forKey:key) {
dict[key.stringValue] = value
} else if let value = try? con.decode(Double.self, forKey:key) {
dict[key.stringValue] = value
} else if let value = try? con.decode(Bool.self, forKey:key) {
dict[key.stringValue] = value
} else if let data = try? con.decode(DictionaryDecodable.self, forKey:key) {
dict[key.stringValue] = data.dictionary
}
}
self.dictionary = dict
}
}
Now you can use this struct to decode dictionary like this:
struct Test: Decodable {
let name: String
let data: [String: Any]
enum Keys: String, CodingKey {
case name
case data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
name = try container.decode(String.self, forKey: .name)
data = try container.decode(DictionaryDecodable.self, forKey: .data).dictionary // Here use DictionaryDecodable
}
}
Let's test:
let data = """
{
"name": "name",
"data": {
"string": "rt",
"bool": true,
"float": 1.12,
"int": 1,
"dict": {
"test": "String"
}
}
}
"""
let s = try JSONDecoder().decode(Test.self, from: data.data(using: .utf8)!)
print(s.name)
print(s.data)
Here's the output:
name
["bool": true, "string": "rt", "int": 1, "float": 1.12, "dict": ["test": "String"]]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With