如何正确解析带有根元素的 JSON 作为 Swift 中的数组?
Posted
技术标签:
【中文标题】如何正确解析带有根元素的 JSON 作为 Swift 中的数组?【英文标题】:How to correctly parse JSON with root element as an array in 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 中的 sonos
和 status
键在哪里?并且名称结构总是以大写字母开头。
你从哪里得到错误?是JSONDecoder().decode
抛出的吗?
谢谢,有帮助。改为 let sonos = try JSONDecoder().decode([sonosStatus].self, from: data)
【参考方案1】:
您的代码有 2 个错误:
-
您要解码的数据是
[[sonosStatus]]
。所以你需要解码为:
let response = try JSONDecoder().decode([[sonosStatus]].self, from: data)
urlPort
代表 String
和 Int
。因此,您需要更正 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 具有 Int
和 String
的 urlPort
值。您可以定义自定义初始化来处理它。
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 的顶层是一个数组,而不是字典。 JSONSerialization
和 allowFragments
解决它。对于 NSDictionary 的强制转换,为什么我们做不到呢?
不,allowFragments
用于原始***类型,例如 String
或 Int
。就 JSON 而言,数组和字典都是对象。并且转换为 NSDictionary 会丢失(强)类型信息,请始终使用本机 Seift 类型。
yaaaa,我明白了;)以上是关于如何正确解析带有根元素的 JSON 作为 Swift 中的数组?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 javascript 解析带有数组的外部 JSON 文件
避免将 null 作为视图根传递(需要在膨胀布局的根元素上解析布局参数)