Json Swift 4可解码,直到解码时才知道密钥
Swift 4可解码协议如何处理包含在运行时才知道其名称的密钥的字典?例如:Json Swift 4可解码,直到解码时才知道密钥,json,swift4,decodable,Json,Swift4,Decodable,Swift 4可解码协议如何处理包含在运行时才知道其名称的密钥的字典?例如: [ { "categoryName": "Trending", "Trending": [ { "category": "Trending", "trailerPrice": "", "isFavourit": null, "isWatchlist": null }
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
这里我们有一系列字典;第一个具有键
categoryName
和Trending
,而第二个具有键categoryName
和喜剧
。categoryName
键的值告诉我第二个键的名称。如何使用Decodable来表示这一点?关键在于如何定义CodingKeys
属性。虽然它通常是一个enum
,但它可以是符合CodingKey
协议的任何东西。要制作动态关键点,可以调用静态函数:
struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}
var name: String
var detail: Detail
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let name = CodingKeys.make(key: "categoryName")
static func make(key: String) -> CodingKeys {
return CodingKeys(stringValue: key)!
}
}
init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
}
}
用法:
let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!
let categories = try! JSONDecoder().decode([Category].self, from: jsonData)
(我将JSON中的
isfavorit
改为isfavorite
,因为我认为这是一个错误销售。如果不是这样的话,修改代码就很容易了)您可以编写一个自定义结构,作为CodingKeys对象,并使用字符串初始化它,以便提取您指定的键:
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
因此,一旦知道所需的键是什么,就可以说(在init(from:)
override:
let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
因此,我最终从解码器中制作了两个容器-一个使用标准CodingKeys enum来提取“categoryName”
键的值,另一个使用CK struct来提取我们刚刚了解的键名的值:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
这是我的整个可解码结构:
struct ResponseData : Codable {
let categoryName : String
let unknown : [Inner]
struct Inner : Codable {
let category : String
let trailerPrice : String
let isFavourit : String?
let isWatchList : String?
}
private enum CodingKeys : String, CodingKey {
case categoryName
}
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: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
}
这是试验台:
let json = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
"""
let myjson = try! JSONDecoder().decode(
[ResponseData].self,
from: json.data(using: .utf8)!)
print(myjson)
这是print语句的输出,证明我们已经正确地填充了结构:
[JustPlaying.ResponseData(
categoryName: "Trending",
unknown: [JustPlaying.ResponseData.Inner(
category: "Trending",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)]),
JustPlaying.ResponseData(
categoryName: "Comedy",
unknown: [JustPlaying.ResponseData.Inner(
category: "Comedy",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)])
]
当然,在现实生活中,我们会有一些错误处理,毫无疑问
编辑后来我意识到(部分归功于CodeDifferent的回答),我不需要两个容器;我可以消除CodingKeys枚举,我的CK结构可以完成所有工作!它是一个通用密钥生成器:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CK.self)
self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
let key = self.categoryName
self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}
下面是我最终想到的json:
let json = """
{
"BTC_BCN":{
"last":"0.00000057",
"percentChange":"0.03636363",
"baseVolume":"47.08463318"
},
"BTC_BELA":{
"last":"0.00001281",
"percentChange":"0.07376362",
"baseVolume":"5.46595029"
}
}
""".data(using: .utf8)!
我们建立这样一个结构:
struct Pair {
let name: String
let details: Details
struct Details: Codable {
let last, percentChange, baseVolume: String
}
}
然后解码:
if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {
var pairs: [Pair] = []
for (name, details) in pairsDictionary {
let pair = Pair(name: name, details: details)
pairs.append(pair)
}
print(pairs)
}
也可以不调用pair.details.baseVolume,而是调用pair.baseVolume:
struct Pair {
......
var baseVolume: String { return details.baseVolume }
......
或编写自定义初始化:
struct Pair {
.....
let baseVolume: String
init(name: String, details: Details) {
self.baseVolume = details.baseVolume
......
当你回答的时候,我想出了一个非常类似的解决方案;我会马上发布,你可以看到你的想法。你的想法显然更好,但我很高兴我独立地想到了一些东西。花了我一整天的时间!这适用于我的情况吗?动态键实际上从来都不知道?有人有任何链接到谈论ab的帖子吗输出动态类型?密钥名是已知的,数据将始终存在,但它可以是字符串或整数。@Martheli发布一个新问题,其中包含问题的详细信息,然后有人会查看。你不会从评论中的问题中得到任何答案。我们基本上提出了相同的解决方案!是的,但你赢得了奖。这永远不会发生对我来说,仅仅使用struct是很困难的,但现在它已经很明显了