【问题标题】:How to correctly parse JSON with root element as an array in Swift?如何正确解析带有根元素的 JSON 作为 Swift 中的数组?
【发布时间】:2020-07-09 10:50:32
【问题描述】:

我有以下问题,我没有正确的结构来解码来自 node-red 的响应以了解我的 Sonos 播放器的状态:

[[{"urlHostname":"192.168.1.1","urlPort":1400,"baseUrl":"http://192.168.1.1:1400","sonosName":"Sonos1","uuid":"RINCON_SONOS14001400","invisible":false},{"urlHostname":"192.168.1.2","urlPort":"1400","baseUrl":"http://192.168.1.2:1400","sonosName":"Sonos2","uuid":"RINCON_SONOS21400","invisible":false},{"urlHostname":"192.168.1.3","urlPort":"1400","baseUrl":"http://192.168.1.3:1400","sonosName":"Sonos3","uuid":"RINCON_SONOS31400","invisible":false},{"urlHostname":"192.168.1.4","urlPort":"1400","baseUrl":"http://192.168.1.4:1400","sonosName":"Sonos4","uuid":"RINCON_SONOS41400","invisible":false},{"urlHostname":"192.168.1.5","urlPort":"1400","baseUrl":"http://192.168.1.5:1400","sonosName":"Sonos5","uuid":"RINCON_SONOS51400","invisible":false},{"urlHostname":"192.168.1.6","urlPort":"1400","baseUrl":"http://192.168.1.6:1400","sonosName":"Sonos6","uuid":"RINCON_SONOS61400","invisible":false}]]

我的结构和解码调用如下所示:

typealias Response = [sonosData]

struct sonosData: Codable {
    let  sonos: [sonosStatusEntry]?
}

struct sonosStatusEntry: Codable {
    let status: [sonosStatus]?
}

struct sonosStatus: Codable {
    let urlHostname: String
    let urlPort: Int
    let baseUrl: String
    let sonosName: String
    let uuid: String
    let invisible: Bool
}

let response = try JSONDecoder().decode(Response.self, from: data)

我在 Swift 中收到以下错误:

Failed to load: The data couldn’t be read because it isn’t in the correct format.

有什么建议吗?

【问题讨论】:

  • 打印错误,而不是 error.localizedDescription。它将有更多信息。您会看到没有sonos 键。应该只是let sonos = try JSONDecoder().decode([sonosStatus].self, from: data)
  • JSON 中的 sonosstatus 键在哪里?并且名称结构总是以大写字母开头。
  • 你从哪里得到错误?是JSONDecoder().decode抛出的吗?
  • 谢谢,有帮助。改为 let sonos = try JSONDecoder().decode([sonosStatus].self, from: data)

标签: arrays json swift jsondecoder


【解决方案1】:

您的代码有 2 个错误:

  1. 您要解码的数据是[[sonosStatus]]。所以你需要解码为:
let response = try JSONDecoder().decode([[sonosStatus]].self, from: data)
  1. urlPort 代表 StringInt。因此,您需要更正 JSON 格式或使用自定义解码器支持这两种格式。

您可以使用这个简单的属性包装器来实现:

@propertyWrapper
struct SomeKindOfInt: Codable {
    var wrappedValue: Int?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let stringifiedValue = try? container.decode(String.self) {
            wrappedValue = Int(stringifiedValue)
        } else {
            wrappedValue = try container.decode(Int.self)
        }
    }
}

然后像这样使用它:

struct sonosStatus: Codable {
    let urlHostname: String
    @SomeKindOfInt var urlPort: Int?
    let baseUrl: String
    let sonosName: String
    let uuid: String
    let invisible: Bool
}

【讨论】:

  • 将两者都添加到我的代码中,现在可以正常工作了!谢谢你的帮助!!!
  • 欢迎您。如果有帮助别忘了点赞。如果这是您问题的答案,也将其标记为已接受的答案。
【解决方案2】:

其中一个问题与urlPort 有关。您的 json 具有 IntStringurlPort 值。您可以定义自定义初始化来处理它。

struct sonosStatus: Codable {
    let urlHostname: String
    let urlPort: Int
    let baseUrl: String
    let sonosName: String
    let uuid: String
    let invisible: Bool    
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        urlHostname = try values.decode(String.self, forKey: .urlHostname)
        baseUrl = try values.decode(String.self, forKey: .baseUrl)
        sonosName = try values.decode(String.self, forKey: .sonosName)
        uuid = try values.decode(String.self, forKey: .uuid)
        invisible = try values.decode(Bool.self, forKey: .invisible)
        
        // you can define urlPort as optional or unwrap it.
        if let intVal = try? values.decode(Int.self, forKey: .urlPort) {
            urlPort = intVal
        } else if let stringVal = try? values.decode(String.self, forKey: .urlPort) {
            urlPort = Int(stringVal) ?? 0
        } else {
            urlPort = ""
        }
    }
}

而且你的 json 是一个 sonosStatus 数组。

所以你需要修改Response typealias,如下所示:

typealias Response = [[sonosStatus]]

【讨论】:

    【解决方案3】:

    您可以将 JSONSerialization 与 .allowFragments 读取选项一起使用。

    let objects = (try?JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [[NSDictionary]]

    它已在最新的 Swift 版本上得到修复:https://bugs.swift.org/browse/SR-6163

    【讨论】:

    • 答案与问题无关。如果预期的类型是数组或字典,allowFragments 是没有意义的。并且永远不要在 Swift 中将 JSON 数据转换为 NS... 集合类型
    • @vadian 我认为根本原因是 json 的顶层是一个数组,而不是字典。 JSONSerializationallowFragments 解决它。对于 NSDictionary 的强制转换,为什么我们做不到呢?
    • 不,allowFragments 用于原始顶级类型,例如 StringInt。就 JSON 而言,数组和字典都是对象。并且转换为 NSDictionary 会丢失(强)类型信息,请始终使用本机 Seift 类型。
    • yaaaa,我明白了;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-12
    • 2019-04-08
    • 2019-04-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多