问题
I have the situation where the server sends me a model where i know of types and names of some keys, and don't about the others. However, the user can edit those other key value pairs of their own volition.
Example:
{ "a": "B",
"b": 42,
"__customKey1": "customVal1",
"__customKey2": [41, 42],
"__customKey3": {"z":"x"}
}
So what i want to end up with is a model with some declared properties and some values stuffed into a Dictionary<String, Any>
, e.g.
struct MyStruct {
var a: String?
var b: Int?
var dict: Dictionary<String,Any>
}
I tried something like:
public struct CodingKeysX: CodingKey {
public var intValue: Int?
public var stringValue: String
public init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
public init?(stringValue: String) { self.stringValue = stringValue }
static func make(key: String) -> CodingKeysX {
return CodingKeysX(stringValue: key)!
}
}
init(from decoder: Decoder) throws {
let co = try! decoder.container(keyedBy: CodingKeysX.self)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.a = try container.decode(String?.self, forKey: .a)
self.b = try container.decode(Int?.self, forKey: .b)
let allDeclaredKeys = container.allKeys.map({ $0.stringValue })
self.dict = Dictionary<String, Any>()
for key in co.allKeys.filter({ !allDeclaredKeys.contains($0.stringValue) }) {
self.dict[key.stringValue] = try? co.decodeIfPresent(Any.self, forKey: key)
}
}
But I get the following compile-time error:
Protocol type 'Any' cannot conform to 'Decodable' because only concrete types can conform to protocols
It also seems that using JSONDecoder
i can't get reference to original Data
to us NSJSONSerialization
. So i could, i suppose, do it the other way round where i first init the dict using the older technique, then init the model using JSONDecoder
and replace init with something passing Data in, but it just feels wrong because we'd be effectively deserializing twice :/
回答1:
I disagree that you should parse this as [String: Any]
. There are only a handful of legal value types in JSON. That's nowhere close to Any
.
Instead, your starting point IMO would be a generic JSON decoder. That decodes into a JSON
enum.
let value = try JSONDecoder().decode(JSON.self, from: json)
==> Optional(["__customKey1": "customVal1", "b": 42, "a": "B", "__customKey2": [41, 42], "__customKey3": ["z": "x"]])
value["a"]?.stringValue // Note value is a JSON, not a Dictionary
==> Optional("B")
Using that, to solve your specific problem you could do something like this (very close to your existing decoder):
struct MyStruct {
var a: String?
var b: Int?
var dict: [String: JSON] // JSON values, not Any values
}
extension MyStruct: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: JSON.Key.self)
let knownKeys = [JSON.Key("a"), JSON.Key("b")]
// Unload the known keys. There's no need for these to be Optional unless
// they really are optional (and nil is different than ""). The point,
// is you can do any "normal" validation you want here and throw on error.
self.a = try container.decodeIfPresent(String.self, forKey: JSON.Key("a"))
self.b = try container.decodeIfPresent(Int.self, forKey: JSON.Key("b"))
// Unload the rest into your dictionary
self.dict = [:]
for key in container.allKeys where !knownKeys.contains(key) {
self.dict[key.stringValue] = try container.decode(JSON.self, forKey: key)
}
}
}
let ms = try JSONDecoder().decode(MyStruct.self, from: json)
=> MyStruct(a: Optional("B"), b: Optional(42),
dict: ["__customKey1": "customVal1",
"__customKey2": [41, 42],
"__customKey3": {"z": "x"}])
ms.dict["__customKey1"]?.stringValue // Optional("customVal1")
来源:https://stackoverflow.com/questions/57351147/swift4-decodable-decode-half-the-keys-as-dictionary