Swift JSON解码提示'id'键缺失但实际存在的解决方法
问题:JSON包含'id'键但解码时提示缺失的原因及解决办法
错误信息
Fatal error: Couldn't parse classification.json as AnimalGroup: keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil))
相关代码与JSON样本
AnimalGroup结构体定义
struct AnimalGroup: Identifiable, Decodable, Hashable { let id: Int let code: Int let form: String? let name: String let shortName: String func hash(into hasher: inout Hasher) { hasher.combine(id) hasher.combine(name) } }
JSON加载与解码函数
var previewAnimal: Animal = load("animal.json") var previewGroup: AnimalGroup = load("classification.json") // Function that loads JSON data into a T type and returns the decoded data func load<T: Decodable>(_ filename: String) -> T { let data: Data guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else { fatalError("Couldn't find \(filename) in main bundle.") } do { data = try Data(contentsOf: file) } catch { fatalError("Couldn't load \(filename) from main bundle:\n\(error)") } do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase // Remember to add this line to avoid running into a crash return try decoder.decode(T.self, from: data) } catch { fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") } }
classification.json样本
{ "group": [ { "code": 3, "form": null, "id": 1, "name": "Vertebrates", "short_name": "CRS" }, { "code": 4, "form": null, "id": 2, "name": "Mammal", "short_name": "CRS" }, { "code": 5, "form": null, "id": 3, "name": "Reptile", "short_name": "CRS" }, { "code": 6, "form": null, "id": 4, "name": "Avians", "short_name": "CRS" }, { "code": 7, "form": null, "id": 5, "name": "Amphibians", "short_name": "CRS" } ] }
原因分析
问题核心是JSON顶层结构与解码目标不匹配:
- 你的JSON顶层是一个包含
group键的对象,group的值才是AnimalGroup类型的数组 - 但你直接尝试将整个JSON解码为单个
AnimalGroup实例,解码器会在JSON顶层寻找id、code等键,自然找不到,因此抛出keyNotFound错误
解决办法
方法1:定义顶层响应结构体(推荐)
新增一个对应JSON顶层结构的结构体,再通过它获取AnimalGroup数组:
struct AnimalGroupResponse: Decodable { let group: [AnimalGroup] }
修改加载代码的类型:
var previewGroup: AnimalGroupResponse = load("classification.json") // 访问数组:previewGroup.group
方法2:直接解码数组(需调整解码逻辑)
如果不想新增结构体,可以在load函数中先解析顶层对象,提取group数组的数据后再解码:
func loadAnimalGroups(from filename: String) -> [AnimalGroup] { let data: Data guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else { fatalError("Couldn't find \(filename) in main bundle.") } do { data = try Data(contentsOf: file) } catch { fatalError("Couldn't load \(filename) from main bundle:\n\(error)") } do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase // 先解码为字典提取group数组 let topLevel = try decoder.decode([String: [AnimalGroup]].self, from: data) guard let groups = topLevel["group"] else { fatalError("No 'group' key found in \(filename)") } return groups } catch { fatalError("Couldn't parse \(filename) as [AnimalGroup]:\n\(error)") } } // 使用时: var previewGroup: [AnimalGroup] = loadAnimalGroups(from: "classification.json")
注意:你的
shortName与JSON中的short_name对应,已经设置了.convertFromSnakeCase,这部分是正确的,无需修改。
内容的提问来源于stack exchange,提问作者chairman
相关产品推荐
相关产品推荐

