如何将NOAA天气JSON解码为Swift结构体
Swift 解析混合类型数组(@context字段处理方案)
核心思路
@context是混合类型数组(包含字符串和对象),而Swift数组要求元素类型统一,因此用枚举封装两种可能的类型,让枚举实现Codable协议,即可完成解析。
具体实现代码
1. 定义Context结构体(对应数组中的对象元素)
先把数组中第二个元素的对象结构映射为Swift结构体:
struct Context: Codable { let version: String let wx: String let geo: String let unit: String let vocab: String // 处理JSON中带@的键名 enum CodingKeys: String, CodingKey { case version = "@version" case wx case geo case unit case vocab = "@vocab" } }
2. 定义枚举封装混合类型
创建枚举ContextElement,包含字符串和Context对象两种情况,并实现Codable协议:
enum ContextElement: Codable { case string(String) case context(Context) // 自定义解码逻辑 init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() // 先尝试解码为字符串 if let stringValue = try? container.decode(String.self) { self = .string(stringValue) } // 失败则尝试解码为Context对象 else if let contextValue = try? container.decode(Context.self) { self = .context(contextValue) } // 都不符合则抛出错误 else { throw DecodingError.dataCorruptedError( in: container, debugDescription: "无法解析为字符串或Context对象" ) } } // 自定义编码逻辑(若不需要编码可省略,但建议保留协议完整性) func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .string(let value): try container.encode(value) case .context(let value): try container.encode(value) } } }
3. 在主结构体中使用枚举类型
在顶层结构体中,将@context字段的类型设为[ContextElement],并通过CodingKeys映射JSON键名:
struct WeatherFeature: Codable { let context: [ContextElement] let type: String let geometry: Geometry let properties: Properties enum CodingKeys: String, CodingKey { case context = "@context" case type case geometry case properties } } // 你已实现的其他结构体示例(按需求补充完整) struct Geometry: Codable { let type: String let coordinates: [[[Double]]] } struct Properties: Codable { let units: String let forecastGenerator: String let generatedAt: String let updateTime: String let validTimes: String let elevation: Elevation let periods: [Period] // 内部结构体(Elevation、Period等)按你已有的实现编写即可 }
4. 解析示例
使用JSONDecoder解析API返回数据时,即可正常处理@context字段:
let jsonData = // 你的API返回数据 do { let weatherFeature = try JSONDecoder().decode(WeatherFeature.self, from: jsonData) // 遍历访问@context内容 for element in weatherFeature.context { switch element { case .string(let url): print("Context URL: \(url)") case .context(let ctx): print("Context版本: \(ctx.version)") print("WX本体地址: \(ctx.wx)") } } } catch { print("解析错误: \(error)") }
说明
- 枚举的自定义
Codable实现是核心,通过singleValueContainer判断当前元素类型,选择对应解码方式。 - 若仅需解码无需编码,可只实现
init(from:)方法,但为了协议完整性建议保留encode(to:)。
内容的提问来源于stack exchange,提问作者logicito
相关产品推荐
相关产品推荐

