动态JSON 解码Swift 4
Dynamic JSON Decoding Swift 4
我正在尝试解码 Swift 4 中的以下 JSON:
{
"token":"RdJY3RuB4BuFdq8pL36w",
"permission":"accounts, users",
"timout_in":600,
"issuer": "Some Corp",
"display_name":"John Doe",
"device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"
}
问题是,JSON 中的最后 2 个元素(display_name
和 device_id
)可能存在也可能不存在,或者这些元素的名称可能完全不同但仍然未知, 即 "fred": "worker", "hours" : 8
所以我想要实现的是解码已知的内容,即 token
、permission
、timeout_in
和 issuer
以及任何其他元素 (display_name
, device_id
etc) 将它们放入字典中。
我的结构是这样的:
struct AccessInfo : Decodable
{
let token: String
let permission: [String]
let timeout: Int
let issuer: String
let additionalData: [String: Any]
private enum CodingKeys: String, CodingKey
{
case token
case permission
case timeout = "timeout_in"
case issuer
}
public init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
token = container.decode(String.self, forKey: .token)
permission = try container.decodeIfPresent(String.self, forKey: .permission).components(separatedBy: ",")
timeout = try container.decode(Int.self, forKey: . timeout)
issuer = container.decode(String.self, forKey: .issuer)
// This is where I'm stuck, how do I add the remaining
// unknown JSON elements into additionalData?
}
}
// Calling code, breviated for clarity
let decoder = JSONDecoder()
let accessInfo = try decoder.decode(AccessInfo.self, from: data!)
能够解码已知结构的一部分,其中 JSON 也可以包含动态信息,如果有人可以提供一些指导,我就在这里。
谢谢
这个问题实际上是 的重复问题。一旦您了解了构建最低限度的 CodingKey 采用者结构作为您的编码密钥的技巧,您就可以将它用于任何字典。
在这种情况下,您将使用键控容器的 allKeys
来获取未知的 JSON 字典键。
为了演示,我将仅限于 JSON 词典中完全未知的部分。想象一下 JSON:
let j = """
{
"display_name":"John Doe",
"device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"
}
"""
let jdata = j.data(using: .utf8)!
假设我们不知道该字典中的内容,除了它具有 String 键和 String 值这一事实。所以我们想在不知道它的键是什么的情况下解析 jdata
。
因此我们有一个由一个字典组成的结构 属性:
struct S {
let stuff : [String:String]
}
现在的问题是如何将 JSON 解析为该结构 - 即如何使该结构符合 Decodable 并处理 JSON。
方法如下:
struct S : Decodable {
let stuff : [String:String]
private struct CK : 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: CK.self)
var d = [String:String]()
for key in con.allKeys {
let value = try! con.decode(String.self, forKey:key)
d[key.stringValue] = value
}
self.stuff = d
}
}
现在我们解析:
let s = try! JSONDecoder().decode(S.self, from: jdata)
然后我们得到一个 S 实例,其 stuff
是这个字典:
["device_id": "uuid824fd3c3-0f69-4ee1-979a-e8ab25558421", "display_name": "John Doe"]
这正是我们想要的结果。
受@matt 评论的启发,这是我使用的完整示例。我扩展了 KeyedDecodingContainer
来解码未知密钥并提供了一个参数来过滤掉已知的 CodingKeys
.
样本JSON
{
"token":"RdJY3RuB4BuFdq8pL36w",
"permission":"accounts, users",
"timout_in":600,
"issuer": "Some Corp",
"display_name":"John Doe",
"device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"
}
Swift 结构
struct AccessInfo : Decodable
{
let token: String
let permission: [String]
let timeout: Int
let issuer: String
let additionalData: [String: Any]
private enum CodingKeys: String, CodingKey
{
case token
case permission
case timeout = "timeout_in"
case issuer
}
public init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
token = container.decode(String.self, forKey: .token)
permission = try container.decode(String.self, forKey: .permission).components(separatedBy: ",")
timeout = try container.decode(Int.self, forKey: . timeout)
issuer = container.decode(String.self, forKey: .issuer)
// Additional data decoding
let container2 = try decoder.container(keyedBy: AdditionalDataCodingKeys.self)
self.additionalData = container2. decodeUnknownKeyValues(exclude: CodingKeys.self)
}
}
private struct AdditionalDataCodingKeys: CodingKey
{
var stringValue: String
init?(stringValue: String)
{
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int)
{
return nil
}
}
KeyedDecodingContainer 扩展
extension KeyedDecodingContainer where Key == AdditionalDataCodingKeys
{
func decodeUnknownKeyValues<T: CodingKey>(exclude keyedBy: T.Type) -> [String: Any]
{
var data = [String: Any]()
for key in allKeys
{
if keyedBy.init(stringValue: key.stringValue) == nil
{
if let value = try? decode(String.self, forKey: key)
{
data[key.stringValue] = value
}
else if let value = try? decode(Bool.self, forKey: key)
{
data[key.stringValue] = value
}
else if let value = try? decode(Int.self, forKey: key)
{
data[key.stringValue] = value
}
else if let value = try? decode(Double.self, forKey: key)
{
data[key.stringValue] = value
}
else if let value = try? decode(Float.self, forKey: key)
{
data[key.stringValue] = value
}
else
{
NSLog("Key %@ type not supported", key.stringValue)
}
}
}
return data
}
}
调用代码
let decoder = JSONDecoder()
let accessInfo = try decoder.decode(AccessInfo.self, from: data!)
print("Token: \(accessInfo.token)")
print("Permission: \(accessInfo.permission)")
print("Timeout: \(accessInfo.timeout)")
print("Issuer: \(accessInfo.issuer)")
print("Additional Data: \(accessInfo.additionalData)")
输出
Token: RdJY3RuB4BuFdq8pL36w
Permission: ["accounts", "users"]
Timeout: 600
Issuer: "Some Corp"
Additional Data: ["display_name":"John Doe", "device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"]
我正在尝试解码 Swift 4 中的以下 JSON:
{
"token":"RdJY3RuB4BuFdq8pL36w",
"permission":"accounts, users",
"timout_in":600,
"issuer": "Some Corp",
"display_name":"John Doe",
"device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"
}
问题是,JSON 中的最后 2 个元素(display_name
和 device_id
)可能存在也可能不存在,或者这些元素的名称可能完全不同但仍然未知, 即 "fred": "worker", "hours" : 8
所以我想要实现的是解码已知的内容,即 token
、permission
、timeout_in
和 issuer
以及任何其他元素 (display_name
, device_id
etc) 将它们放入字典中。
我的结构是这样的:
struct AccessInfo : Decodable
{
let token: String
let permission: [String]
let timeout: Int
let issuer: String
let additionalData: [String: Any]
private enum CodingKeys: String, CodingKey
{
case token
case permission
case timeout = "timeout_in"
case issuer
}
public init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
token = container.decode(String.self, forKey: .token)
permission = try container.decodeIfPresent(String.self, forKey: .permission).components(separatedBy: ",")
timeout = try container.decode(Int.self, forKey: . timeout)
issuer = container.decode(String.self, forKey: .issuer)
// This is where I'm stuck, how do I add the remaining
// unknown JSON elements into additionalData?
}
}
// Calling code, breviated for clarity
let decoder = JSONDecoder()
let accessInfo = try decoder.decode(AccessInfo.self, from: data!)
能够解码已知结构的一部分,其中 JSON 也可以包含动态信息,如果有人可以提供一些指导,我就在这里。
谢谢
这个问题实际上是
在这种情况下,您将使用键控容器的 allKeys
来获取未知的 JSON 字典键。
为了演示,我将仅限于 JSON 词典中完全未知的部分。想象一下 JSON:
let j = """
{
"display_name":"John Doe",
"device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"
}
"""
let jdata = j.data(using: .utf8)!
假设我们不知道该字典中的内容,除了它具有 String 键和 String 值这一事实。所以我们想在不知道它的键是什么的情况下解析 jdata
。
因此我们有一个由一个字典组成的结构 属性:
struct S {
let stuff : [String:String]
}
现在的问题是如何将 JSON 解析为该结构 - 即如何使该结构符合 Decodable 并处理 JSON。
方法如下:
struct S : Decodable {
let stuff : [String:String]
private struct CK : 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: CK.self)
var d = [String:String]()
for key in con.allKeys {
let value = try! con.decode(String.self, forKey:key)
d[key.stringValue] = value
}
self.stuff = d
}
}
现在我们解析:
let s = try! JSONDecoder().decode(S.self, from: jdata)
然后我们得到一个 S 实例,其 stuff
是这个字典:
["device_id": "uuid824fd3c3-0f69-4ee1-979a-e8ab25558421", "display_name": "John Doe"]
这正是我们想要的结果。
受@matt 评论的启发,这是我使用的完整示例。我扩展了 KeyedDecodingContainer
来解码未知密钥并提供了一个参数来过滤掉已知的 CodingKeys
.
样本JSON
{
"token":"RdJY3RuB4BuFdq8pL36w",
"permission":"accounts, users",
"timout_in":600,
"issuer": "Some Corp",
"display_name":"John Doe",
"device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"
}
Swift 结构
struct AccessInfo : Decodable
{
let token: String
let permission: [String]
let timeout: Int
let issuer: String
let additionalData: [String: Any]
private enum CodingKeys: String, CodingKey
{
case token
case permission
case timeout = "timeout_in"
case issuer
}
public init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
token = container.decode(String.self, forKey: .token)
permission = try container.decode(String.self, forKey: .permission).components(separatedBy: ",")
timeout = try container.decode(Int.self, forKey: . timeout)
issuer = container.decode(String.self, forKey: .issuer)
// Additional data decoding
let container2 = try decoder.container(keyedBy: AdditionalDataCodingKeys.self)
self.additionalData = container2. decodeUnknownKeyValues(exclude: CodingKeys.self)
}
}
private struct AdditionalDataCodingKeys: CodingKey
{
var stringValue: String
init?(stringValue: String)
{
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int)
{
return nil
}
}
KeyedDecodingContainer 扩展
extension KeyedDecodingContainer where Key == AdditionalDataCodingKeys
{
func decodeUnknownKeyValues<T: CodingKey>(exclude keyedBy: T.Type) -> [String: Any]
{
var data = [String: Any]()
for key in allKeys
{
if keyedBy.init(stringValue: key.stringValue) == nil
{
if let value = try? decode(String.self, forKey: key)
{
data[key.stringValue] = value
}
else if let value = try? decode(Bool.self, forKey: key)
{
data[key.stringValue] = value
}
else if let value = try? decode(Int.self, forKey: key)
{
data[key.stringValue] = value
}
else if let value = try? decode(Double.self, forKey: key)
{
data[key.stringValue] = value
}
else if let value = try? decode(Float.self, forKey: key)
{
data[key.stringValue] = value
}
else
{
NSLog("Key %@ type not supported", key.stringValue)
}
}
}
return data
}
}
调用代码
let decoder = JSONDecoder()
let accessInfo = try decoder.decode(AccessInfo.self, from: data!)
print("Token: \(accessInfo.token)")
print("Permission: \(accessInfo.permission)")
print("Timeout: \(accessInfo.timeout)")
print("Issuer: \(accessInfo.issuer)")
print("Additional Data: \(accessInfo.additionalData)")
输出
Token: RdJY3RuB4BuFdq8pL36w
Permission: ["accounts", "users"]
Timeout: 600
Issuer: "Some Corp"
Additional Data: ["display_name":"John Doe", "device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"]