Swift 4可解码,直到解码时间才知道密钥

mat*_*att 48 json swift4 decodable

Swift 4可解码协议如何处理包含密钥的字典,该密钥的名称在运行时才知道?例如:

  [
    {
      "categoryName": "Trending",
      "Trending": [
        {
          "category": "Trending",
          "trailerPrice": "",
          "isFavourit": null,
          "isWatchlist": null
        }
      ]
    },
    {
      "categoryName": "Comedy",
      "Comedy": [
        {
          "category": "Comedy",
          "trailerPrice": "",
          "isFavourit": null,
          "isWatchlist": null
        }
      ]
    }
  ]
Run Code Online (Sandbox Code Playgroud)

这里我们有一系列字典; 所述第一有键categoryNameTrending,而第二个有密钥categoryNameComedy.categoryName键的值告诉我第二个键的名称.如何使用Decodable表达?

Cod*_*ent 48

关键在于如何定义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!
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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)
Run Code Online (Sandbox Code Playgroud)

(我isFavourit在JSON中改变了,isFavourite因为我认为这是一个错误的拼写.如果不是这样的话,很容易调整代码)

  • 显然,你的更好,但我很高兴我独立思考了_某事_。带了我一整天! (2认同)

mat*_*att 5

您可以编写一个用作CodingKeys对象的自定义结构,并使用字符串对其进行初始化,以便提取您指定的键:

private struct CK : CodingKey {
    var stringValue: String
    init?(stringValue: String) {
        self.stringValue = stringValue
    }
    var intValue: Int?
    init?(intValue: Int) {
        return nil
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,一旦你知道所需的键是什么,你可以说(在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)!)
Run Code Online (Sandbox Code Playgroud)

所以我最终做的是从解码器中创建两个容器 - 一个使用标准的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)!)
}
Run Code Online (Sandbox Code Playgroud)

那么,这是我的整个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)!)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是试验台:

    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)
Run Code Online (Sandbox Code Playgroud)

这是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)])
]
Run Code Online (Sandbox Code Playgroud)

当然,在现实生活中我们会有一些错误处理,毫无疑问!


编辑后来我意识到(部分归功于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)!)
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,但你赢了奖.我从来没有想过只使用结构.但现在它显而易见了.:) (3认同)