如何用JSONDecoder的unkeyedContainer解码外层为方括号的JSON数组
问题背景
给定如下JSON数组结构:
["mJv8P81",["0","4B43AC",4,1675445326,2,3,1,47.40967,8.9179,46,956,0,267,0,0,0,7,3,0,"HB-ZQI"],["0","DE007A",9,1675445313,2,5,1,50.42467,9.19857,0,518,0,180,0,0,0,5,3,0,"TEST"],["0","DD0B25",9,1675445322,2,3,1,51.9966,8.55458,39,454,-2,293,0,0,0,7,3,0,"D-HVBI"],["0","D005CE",9,1675445326,2,3,1,52.40545,9.77742,40,304,0,324,0,0,0,7,3,0,"D-HDSX"],["0","3B7B9E",4,1675445308,2,3,1,43.89118,4.92555,47,454,-2,330,0,0,0,7,3,0,"F-ZBPM"]]
需要用Foundation框架的JSONDecoder(结合unkeyedContainer)完成解码。已定义Decodable结构体,但解码后batchID和data均为nil,直接调用解码方法也报错,提示解码器期望键值对。
问题分析
adslAircraft未适配数组解码:现有结构体默认按键值对解码,但JSON中每个飞机数据是无键数组,必须自定义init(from:)逻辑逐个解析数组元素。- 时间戳未转换:JSON中的
timestamp是秒级Unix时间戳,需处理数字到Date类型的转换,否则解码会失败。 - 顶层解码逻辑错误:现有
udpSafeSkyPost的init(from:)尝试直接解码[adslAircraft].self,但解码器会期望嵌套数组,而实际是多个并列的数组元素,无法直接批量解码。
解决方案
1. 修正结构体实现
为adslAircraft添加自定义解码逻辑,同时完善udpSafeSkyPost的解码流程:
struct udpSafeSkyPost: Decodable { struct adslAircraft: Decodable { let version: String let address: String let address_type: UInt8 let timestamp: Date let state: UInt8 let category: UInt8 let eStatus: UInt8 let lat: Double let lon: Double let speed: UInt32 let altitude: UInt32 let vRate: Int32 let dir: UInt32 let sil: UInt8 let dass: UInt8 let navil: UInt8 let accH: UInt8 let accV: UInt8 let accVeloc: UInt8 let extID: String init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() // 严格按JSON数组的顺序逐个解码元素 version = try container.decode(String.self) address = try container.decode(String.self) address_type = try container.decode(UInt8.self) // 手动转换秒级时间戳为Date let timestampInt = try container.decode(Int.self) timestamp = Date(timeIntervalSince1970: TimeInterval(timestampInt)) state = try container.decode(UInt8.self) category = try container.decode(UInt8.self) eStatus = try container.decode(UInt8.self) lat = try container.decode(Double.self) lon = try container.decode(Double.self) speed = try container.decode(UInt32.self) altitude = try container.decode(UInt32.self) vRate = try container.decode(Int32.self) dir = try container.decode(UInt32.self) sil = try container.decode(UInt8.self) dass = try container.decode(UInt8.self) navil = try container.decode(UInt8.self) accH = try container.decode(UInt8.self) accV = try container.decode(UInt8.self) accVeloc = try container.decode(UInt8.self) extID = try container.decode(String.self) } } let batchID: String let data: [adslAircraft] init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() // 解码第一个元素作为batchID batchID = try container.decode(String.self) // 循环解码剩余所有飞机数组 var aircrafts = [adslAircraft]() while !container.isAtEnd { let aircraft = try container.decode(adslAircraft.self) aircrafts.append(aircraft) } data = aircrafts } }
2. 正确调用解码方法
设置解码器配置并执行解码(也可选择通过解码器全局配置处理时间戳,替代结构体内部的手动转换):
let jsonArrayDecoder = JSONDecoder() // 可选:如果不在adslAircraft内部处理时间戳,可启用此全局策略 // jsonArrayDecoder.dateDecodingStrategy = .secondsSince1970 do { guard let data = data else { throw NSError(domain: "DataError", code: -1, userInfo: [NSLocalizedDescriptionKey: "数据为空"]) } let safeSkyModel = try jsonArrayDecoder.decode(udpSafeSkyPost.self, from: data) // 解码成功后使用模型 print("批次ID:\(safeSkyModel.batchID)") print("飞机数量:\(safeSkyModel.data.count)") } catch { print("解码失败:\(error.localizedDescription)") print("详细错误信息:\(error)") }
关键注意点
- 无键数组解码:顶层
udpSafeSkyPost和内部adslAircraft都必须通过unkeyedContainer逐个解析数组元素,不能依赖默认的键值对解码逻辑。 - 时间戳处理:JSON中的时间戳是秒级整数,可选择在结构体内部手动转换,或设置解码器的
dateDecodingStrategy为.secondsSince1970。 - 循环解码剩余元素:顶层解码时不能直接调用
decode([adslAircraft].self),因为解码器会期望嵌套数组结构,必须循环解码直到容器结束。
内容的提问来源于stack exchange,提问作者jumbopilot
相关产品推荐
相关产品推荐

