【问题标题】:Swift 4 Decodable with keys not known until decoding timeSwift 4 可解码,直到解码时才知道密钥
【发布时间】:2018-01-17 19:15:59
【问题描述】:

Swift 4 Decodable 协议如何处理包含一个直到运行时才知道名称的键的字典?例如:

  [
    {
      "categoryName": "Trending",
      "Trending": [
        {
          "category": "Trending",
          "trailerPrice": "",
          "isFavourit": null,
          "isWatchlist": null
        }
      ]
    },
    {
      "categoryName": "Comedy",
      "Comedy": [
        {
          "category": "Comedy",
          "trailerPrice": "",
          "isFavourit": null,
          "isWatchlist": null
        }
      ]
    }
  ]

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

【问题讨论】:

    标签: json swift4 decodable


    【解决方案1】:

    这是我最终想出的这个 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
        ......
    

    【讨论】:

      【解决方案2】:

      关键在于如何定义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 中的 isFavourit 更改为 isFavourite,因为我认为这是拼写错误。如果不是这种情况,修改代码很容易)

      【讨论】:

      • 当您回答时,我想出了一个非常相似的解决方案;我一会儿贴出来,你看看你的想法。
      • 显然你的更好,但我很高兴我独立地想到了一些东西。花了我一整天!
      • 这是否适用于动态键实际上永远不知道的情况? stackoverflow.com/questions/46726415/…
      • 谁有任何关于动态类型的帖子的链接?键名是已知的,数据将始终存在,但它可以是字符串或 Int。
      • @Martheli 发布一个新问题,其中包含您的问题的详细信息,有人会查看它。您不会从评论中的问题中得到任何答案
      【解决方案3】:

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

      那么,这里是我的整个 Decodable 结构:

      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)!)
      }
      

      【讨论】:

      • 我们基本上想出了相同的解决方案!
      • 是的,但是你赢了。我从来没有想过单独使用结构。然而现在已经很明显了。 :)
      猜你喜欢
      • 1970-01-01
      • 2018-03-26
      • 1970-01-01
      • 1970-01-01
      • 2017-11-26
      • 1970-01-01
      • 2018-03-17
      • 2019-01-11
      相关资源
      最近更新 更多