Swift 4直到解码时间才能使用密钥进行解码
Swift 4 Decodable协议如何处理包含直到运行时才知道其名称的密钥的字典?例如:Swift 4直到解码时间才能使用密钥进行解码
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
这里我们有一个字典数组;第一个具有键categoryName
和Trending
,而第二个具有键categoryName
和Comedy
。 categoryName
键的值告诉我第二个键的名称。我如何使用可解码来表达这一点?
关键在于您如何定义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
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(stringValue: "categoryName")!
static func makeKey(name: String) -> CodingKeys {
return CodingKeys(stringValue: name)!
}
}
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: .makeKey(name: 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改变isFavourit
到isFavourite
,因为我认为这是一个拼写错误这是很容易如果不是这种情况,请修改代码)
您可以编写一个用作CodingKeys对象的自定义结构,并使用字符串初始化它,以便提取指定的键:
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
因此,一旦你知道所需的关键是什么,你可以说(在init(from:)
覆盖:
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枚举提取"categoryName"
键的值,而另一个使用CK结构来提取关键的名字,我们刚学的价值:
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)!)
}
我们基本上想出了相同的解决方案! –
是的,但你赢得了奖项。我从来没有想过只使用结构。但现在它非常明显。 :) – matt
当你回答时,我想出了一个非常类似的解决方案;我会在一会儿发布它,你可以看到你的想法。 – matt
显然,你的表现更好,但我很高兴我独立思考了_something_。结束了我整天! – matt
这就是为什么这个网站是如此伟大的学习 –