Swift中用Decodable解析自定义JSON失败,报类型不匹配错误
解决Swift JSON解码错误的方案
首先,我们来拆解你遇到的问题:
错误核心原因
- 顶层类型不匹配:你的原始JSON是一个字典(
{}),但你的代码尝试将它解码成数组([]),这就是错误提示里Expected to decode Array<Any> but found a dictionary instead的由来。 - 结构体定义与JSON结构不匹配:你定义的
decodeJson和secondLayer的属性名(symbol、exchange)和JSON里的实际键完全对应不上,而且层级结构也搞错了。
正确的解码方案
你的JSON结构可以拆解为:
- 顶层是一个字典,键为
first/second/third等动态字符串 - 每个顶层键对应的值又是一个字典,键为
house/car/ice等动态字符串,值是字符串数组
针对这种动态键的结构,我们可以直接用嵌套字典来解码,不需要额外定义复杂的结构体(当然也可以定义结构体,不过直接用字典更简洁)。
修正后的解码代码
static func decode(jsonString: String) -> [String: [String: [String]]]? { let decoder = JSONDecoder() guard let jsonData = jsonString.data(using: .utf8) else { print("无法将字符串转换为Data") return nil } do { // 直接解码为嵌套字典:顶层键 -> 次级键 -> 字符串数组 let decodedData = try decoder.decode([String: [String: [String]]].self, from: jsonData) return decodedData } catch { print("解码错误:\(error.localizedDescription)") print("详细错误信息:\(error)") return nil } }
如果一定要用结构体定义
如果你希望用结构体来明确类型(虽然这里键都是动态的,结构体优势不大),可以这样定义:
// 次级结构:对应house/car等键的容器 struct PropertyContainer: Codable { // 因为键是动态的,用[String: [String]]来接收 var properties: [String: [String]] // 自定义键解码,直接把所有键值对映射到properties属性 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) var props = [String: [String]]() for key in container.allKeys { let value = try container.decode([String].self, forKey: key) props[key.stringValue] = value } self.properties = props } // 自定义编码(如果需要编码的话) func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) for (key, value) in properties { let codingKey = CodingKeys(stringValue: key)! try container.encode(value, forKey: codingKey) } } // 动态CodingKey实现 private struct CodingKeys: CodingKey { let stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? { return nil } init?(intValue: Int) { return nil } } } // 顶层结构:对应first/second等键的容器 struct Root: Codable { var items: [String: PropertyContainer] // 同样自定义解码,把顶层动态键映射到items属性 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) var itemDict = [String: PropertyContainer]() for key in container.allKeys { let value = try container.decode(PropertyContainer.self, forKey: key) itemDict[key.stringValue] = value } self.items = itemDict } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) for (key, value) in items { let codingKey = CodingKeys(stringValue: key)! try container.encode(value, forKey: codingKey) } } private struct CodingKeys: CodingKey { let stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? { return nil } init?(intValue: Int) { return nil } } } // 对应的解码函数 static func decodeWithStruct(jsonString: String) -> Root? { let decoder = JSONDecoder() guard let jsonData = jsonString.data(using: .utf8) else { print("无法将字符串转换为Data") return nil } do { let decodedData = try decoder.decode(Root.self, from: jsonData) return decodedData } catch { print("解码错误:\(error.localizedDescription)") print("详细错误信息:\(error)") return nil } }
额外注意事项
- 不要使用
try!和强制解包!,这些会在出错时直接崩溃,应该用guard let或if let处理可选值,用do-catch捕获错误。 - 结构体的命名遵循Swift规范,应该用大驼峰(比如
DecodeJson改为Root,SecondLayer改为PropertyContainer),提高代码可读性。
内容的提问来源于stack exchange,提问作者PaFi
相关产品推荐
相关产品推荐

