Swift Codable:如何 "pass through" 一个 json 未分析的子对象
Swift Codable: how to "pass through" a json sub-object unanalysed
由于复杂的原因,我发现自己在违背 Codable
的原则:在解码 json 对象时,我想保留键 extra
[=30 下的子对象=],存储为 [String: Any]
字典,但 [String: Any]
(当然)不是 Decodable
。有什么办法吗?
第一个回答当然是"don't"。我的回答是"I need to":我需要让两个Codable
传递数据,其中第一个解码异构对象列表(每个对象都有一个键 name
),而第二遍使用以这些 name
值为键的字典,并且是正确的类型安全的。第一遍不能是类型安全的,因为它在异构列表上运行,但它需要保留第二遍将使用的所有数据。值得庆幸的是,所有异构数据都隐藏在那个 extra
键下,但我仍然不知道该怎么做。
(很可能会有关于 _en_coding 相同问题的后续问题,所以如果您碰巧有见识,请随时提出。)
我得到的最接近的是以下 Playgrounds 代码:
struct Foo: Codable {
let bar: [String: Any]
enum CodingKeys: String, CodingKey {
case bar
}
init(bar: [String: Any]) {
self.bar = bar
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let barString = try values.decode(String.self, forKey: .bar)
let barData = Data(barString.utf8)
let json: [String: Any] = try JSONSerialization.jsonObject(with: barData, options: .allowFragments) as! [String: Any]
bar = json
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let barData = try JSONSerialization.data(withJSONObject: bar, options: .sortedKeys)
let barString = String(decoding: barData, as: UTF8.self)
try container.encode(barString, forKey: .bar)
}
}
let foo = Foo(bar: ["someInt": 1])
let fooData = try! JSONEncoder().encode(foo)
print(String(decoding: fooData, as: UTF8.self))
print(String(decoding: fooData, as: UTF8.self).replacingOccurrences(of: "\\"", with: "\""))
let decodedFoo = try! JSONDecoder().decode(Foo.self, from: fooData)
print(decodedFoo)
以上打印:
{"bar":"{\"someInt\":1}"}
{"bar":"{"someInt":1}"}
Foo(bar: ["someInt": 1])
它还不完美,因为它在生成的 JSON 字符串中转义了 "
,因此可能会破坏其中的某些内容。额外的字符串替换过程可能不是一件令人愉快的事情。
您可以创建一个自定义字典 Decodable
,它将值解码并存储在 Dictionary
中,然后将此类型用作要存储原始字典的键。
这是解码器:
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
}
}
现在你可以像这样使用这个结构来解码字典:
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 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)
这是输出:
name
["bool": true, "string": "rt", "int": 1, "float": 1.12, "dict": ["test": "String"]]
这 Swift Forums post 似乎提供了解决方案。它是这样开始的:
public enum JSON : Codable {
case null
case number(NSNumber)
case string(String)
case array([JSON])
case dictionary([String : JSON])
// ...
想法是制作一个明确的 JSON
枚举类型,即 Codable
,直接编码 json 结构(case string(String); case array([JSON]);
等)。未分析的子对象被强类型化为 JSON
并且可以通过 Codable
成为 encoded/decoded,并且(作为奖励)也可以使用 "just Swift" 进行分析(打开case and do stuff) 如有必要。
由于复杂的原因,我发现自己在违背 Codable
的原则:在解码 json 对象时,我想保留键 extra
[=30 下的子对象=],存储为 [String: Any]
字典,但 [String: Any]
(当然)不是 Decodable
。有什么办法吗?
第一个回答当然是"don't"。我的回答是"I need to":我需要让两个Codable
传递数据,其中第一个解码异构对象列表(每个对象都有一个键 name
),而第二遍使用以这些 name
值为键的字典,并且是正确的类型安全的。第一遍不能是类型安全的,因为它在异构列表上运行,但它需要保留第二遍将使用的所有数据。值得庆幸的是,所有异构数据都隐藏在那个 extra
键下,但我仍然不知道该怎么做。
(很可能会有关于 _en_coding 相同问题的后续问题,所以如果您碰巧有见识,请随时提出。)
我得到的最接近的是以下 Playgrounds 代码:
struct Foo: Codable {
let bar: [String: Any]
enum CodingKeys: String, CodingKey {
case bar
}
init(bar: [String: Any]) {
self.bar = bar
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let barString = try values.decode(String.self, forKey: .bar)
let barData = Data(barString.utf8)
let json: [String: Any] = try JSONSerialization.jsonObject(with: barData, options: .allowFragments) as! [String: Any]
bar = json
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let barData = try JSONSerialization.data(withJSONObject: bar, options: .sortedKeys)
let barString = String(decoding: barData, as: UTF8.self)
try container.encode(barString, forKey: .bar)
}
}
let foo = Foo(bar: ["someInt": 1])
let fooData = try! JSONEncoder().encode(foo)
print(String(decoding: fooData, as: UTF8.self))
print(String(decoding: fooData, as: UTF8.self).replacingOccurrences(of: "\\"", with: "\""))
let decodedFoo = try! JSONDecoder().decode(Foo.self, from: fooData)
print(decodedFoo)
以上打印:
{"bar":"{\"someInt\":1}"}
{"bar":"{"someInt":1}"}
Foo(bar: ["someInt": 1])
它还不完美,因为它在生成的 JSON 字符串中转义了 "
,因此可能会破坏其中的某些内容。额外的字符串替换过程可能不是一件令人愉快的事情。
您可以创建一个自定义字典 Decodable
,它将值解码并存储在 Dictionary
中,然后将此类型用作要存储原始字典的键。
这是解码器:
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
}
}
现在你可以像这样使用这个结构来解码字典:
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 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)
这是输出:
name
["bool": true, "string": "rt", "int": 1, "float": 1.12, "dict": ["test": "String"]]
这 Swift Forums post 似乎提供了解决方案。它是这样开始的:
public enum JSON : Codable {
case null
case number(NSNumber)
case string(String)
case array([JSON])
case dictionary([String : JSON])
// ...
想法是制作一个明确的 JSON
枚举类型,即 Codable
,直接编码 json 结构(case string(String); case array([JSON]);
等)。未分析的子对象被强类型化为 JSON
并且可以通过 Codable
成为 encoded/decoded,并且(作为奖励)也可以使用 "just Swift" 进行分析(打开case and do stuff) 如有必要。