2017-08-09 51 views
15

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 
     } 
     ] 
    } 
    ] 

这里我们有一个字典数组;第一个具有键categoryNameTrending,而第二个具有键categoryNameComedycategoryName键的值告诉我第二个键的名称。我如何使用可解码来表达这一点?

回答

15

关键在于您如何定义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改变isFavouritisFavourite,因为我认为这是一个拼写错误这是很容易如果不是这种情况,请修改代码)

+0

当你回答时,我想出了一个非常类似的解决方案;我会在一会儿发布它,你可以看到你的想法。 – matt

+0

显然,你的表现更好,但我很高兴我独立思考了_something_。结束了我整天! – matt

+0

这就是为什么这个网站是如此伟大的学习 –

4

您可以编写一个用作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)!) 
} 
+0

我们基本上想出了相同的解决方案! –

+2

是的,但你赢得了奖项。我从来没有想过只使用结构。但现在它非常明显。 :) – matt