【问题标题】:Swift Codable - Parse JSON array which can contain different data typeSwift Codable - 解析可以包含不同数据类型的 JSON 数组
【发布时间】:2018-06-27 03:18:20
【问题描述】:

我正在尝试解析一个 JSON 数组,它可以是

{
  "config_data": [
      {
        "name": "illuminate",
        "config_title": "Blink"
      },
      {
        "name": "shoot",
        "config_title": "Fire"
      }
    ]
}

也可以是以下类型

{
  "config_data": [
          "illuminate",
          "shoot"
        ]
}

甚至

{
    "config_data": [
              25,
              100
            ]
  }

所以为了使用 JSONDecoder 解析它,我创建了一个结构如下 -

Struct Model: Codable {
  var config_data: [Any]?

  enum CodingKeys: String, CodingKey {
    case config_data = "config_data"
   }

  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    config_data = try values.decode([Any].self, forKey: .config_data)
  }
}

但这不起作用,因为 Any 不确认可解码协议。这可能是什么解决方案。数组可以包含任何类型的数据

【问题讨论】:

    标签: swift swift4 jsondecoder


    【解决方案1】:

    您正在尝试 JSON to object 或 object to JSON ?您可以尝试使用此代码添加任何 swift 文件:

    extension String {
        var xl_json: Any? {
            if let data = data(using: String.Encoding.utf8) {
                return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
            }
            return nil
        }
    }
    
    extension Array {
        var xl_json: String? {
            guard let data = try? JSONSerialization.data(withJSONObject: self, options: []) else {
                return nil
            }
            return String(data: data, encoding: .utf8)
        }
    }
    
    extension Dictionary {
        var xl_json: String? {
            guard let data = try? JSONSerialization.data(withJSONObject: self, options: []) else {
                return nil
            }
            return String(data: data, encoding: .utf8)
        }
    }
    

    并运行此代码:

    let str = "{\"key\": \"Value\"}"
    let dict = str.xl_json as! [String: String] // JSON to Objc
    let json = dict.xl_json                     // Objc to JSON
    
    print("jsonStr - \(str)")
    print("objc - \(dict)")
    print("jsonStr - \(json ?? "nil")")
    

    最后,你会得到它:

    jsonStr - {"key": "Value"}
    objc - ["key": "Value"]
    jsonStr - {"key":"Value"}
    

    【讨论】:

    • JSONSerialization 总是更容易执行,但是使用 Swift 4 和 JSONDecoder,这是我面临的问题
    【解决方案2】:

    您首先需要决定如果出现第二个 JSON 该怎么办。第二种 JSON 格式的信息要少得多。您想对丢失的那些数据 (config_title) 做什么?你真的需要它们吗?

    如果您确实需要存储config_titles(如果它们存在),那么我建议您创建一个ConfigItem 结构,如下所示:

    struct ConfigItem: Codable {
        let name: String
        let configTitle: String?
    
        init(name: String, configTitle: String? = nil) {
            self.name = name
            self.configTitle = configTitle
        }
    
        // encode and init(decoder:) here...
        // ...
    }
    

    实现所需的encode 和init(decoder:) 方法。你懂的。

    现在,当您解码 JSON 时,像往常一样解码 config_data 键。但是这一次,您可以解码为[ConfigItem],而不是使用[Any]!显然这并不总是有效,因为 JSON 有时可能是第二种形式。因此,您可以捕获由此引发的任何错误,并改用[String] 解码config_data。然后,将字符串数组映射到一堆ConfigItems!

    【讨论】:

    • 更新了我的问题。基本的事情是数组可以包含任何类型的数据
    • @GaneshSomani 正如我在开头所说的,如果 JSON 可以是 anything,你想对它做什么?你最终想要做什么?
    • 这是一些支持我的主要数据的多余信息...比如说我正在存储一个计数,所以它可以是我可以从服务器获取的初始起始计数。或者这是音乐数据的额外信息,然后它可以包含专辑封面的 URL
    • @GaneshSomani 所以你知道你得到了什么。您可以从“主要数据”中分辨出来吗?然后为每种情况创建一个结构。并使用正确的结构类型进行解码。否则,您必须使用 Swift 3 方式将 JSON 转换为字典。
    【解决方案3】:

    我使用quicktype 来推断config_data 的类型,它为您的对象、字符串和整数值建议了一个带有不同大小写的枚举:

    struct ConfigData {
        let configData: [ConfigDatumElement]
    }
    
    enum ConfigDatumElement {
        case configDatumClass(ConfigDatumClass)
        case integer(Int)
        case string(String)
    }
    
    struct ConfigDatumClass {
        let name, configTitle: String
    }
    

    这里是the complete code example。解码 enum 有点棘手,但 quicktype 可以帮助您:

    // To parse the JSON, add this file to your project and do:
    //
    //   let configData = try? JSONDecoder().decode(ConfigData.self, from: jsonData)
    
    import Foundation
    
    struct ConfigData: Codable {
        let configData: [ConfigDatumElement]
    
        enum CodingKeys: String, CodingKey {
            case configData = "config_data"
        }
    }
    
    enum ConfigDatumElement: Codable {
        case configDatumClass(ConfigDatumClass)
        case integer(Int)
        case string(String)
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let x = try? container.decode(Int.self) {
                self = .integer(x)
                return
            }
            if let x = try? container.decode(String.self) {
                self = .string(x)
                return
            }
            if let x = try? container.decode(ConfigDatumClass.self) {
                self = .configDatumClass(x)
                return
            }
            throw DecodingError.typeMismatch(ConfigDatumElement.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ConfigDatumElement"))
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .configDatumClass(let x):
                try container.encode(x)
            case .integer(let x):
                try container.encode(x)
            case .string(let x):
                try container.encode(x)
            }
        }
    }
    
    struct ConfigDatumClass: Codable {
        let name, configTitle: String
    
        enum CodingKeys: String, CodingKey {
            case name
            case configTitle = "config_title"
        }
    }
    

    使用enum 很好,因为这样可以获得最大的类型安全性。其他答案似乎失去了这一点。

    使用 quicktype 的便捷初始化选项,一个工作代码示例是:

    let data = try ConfigData("""
    {
      "config_data": [
        {
          "name": "illuminate",
          "config_title": "Blink"
        },
        {
          "name": "shoot",
          "config_title": "Fire"
        },
        "illuminate",
        "shoot",
        25,
        100
      ]
    }
    """)
    
    for item in data.configData {
        switch item {
        case .configDatumClass(let d):
            print("It's a class:", d)
        case .integer(let i):
            print("It's an int:", i)
        case .string(let s):
            print("It's a string:", s)
        }
    }
    

    打印出来:

    It's a class: ConfigDatumClass(name: "illuminate", configTitle: "Blink")
    It's a class: ConfigDatumClass(name: "shoot", configTitle: "Fire")
    It's a string: illuminate
    It's a string: shoot
    It's an int: 25
    It's an int: 100
    

    【讨论】:

    • 我只是在经历这个,并试图了解我们如何从configData: [ConfigDatumElement]获取价值
    • 我添加了一个工作代码示例——您可以遍历该数组,并使用我演示的 switch 语句。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多