Swift JSON解码报错:CodingKeys('row')未找到,寻求解决方案
解决Swift Codable解码API响应时的"row"键缺失错误
错误信息
"Key 'CodingKeys(stringValue: "row", intValue: nil)' not found: No value associated with key CodingKeys(stringValue: "row", intValue: nil) ("row"). codingPath: [CodingKeys(stringValue: "schoolInfo", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)]"
Swift Key 'CodingKeys()' not found: No value associated with key CodingKeys() ("row")
问题根源
API返回的JSON中,schoolInfo是包含两个对象的数组:
- 第一个对象含
head字段,存储请求状态、数据总数等元信息 - 第二个对象含
row字段,才是实际的学校数据
而你定义的schoolRow结构体强制要求row字段存在,解码数组第一个元素(无row键)时直接触发错误。
解决方案
方案一:使用枚举区分元素类型(推荐)
通过枚举明确处理schoolInfo数组中的两种元素类型,结构清晰,避免可选值的模糊性。
- 定义元数据相关结构体:
// 处理head中的数据结构 struct HeadInfo: Codable { let listTotalCount: Int? let result: ResultInfo? private enum CodingKeys: String, CodingKey { case listTotalCount = "list_total_count" case result = "RESULT" } } struct ResultInfo: Codable { let code: String let message: String private enum CodingKeys: String, CodingKey { case code = "CODE" case message = "MESSAGE" } }
- 定义枚举处理
schoolInfo的两种元素:
enum SchoolInfoElement: Codable { case head([HeadInfo]) case row([SchoolsInfo]) init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.head) { let headData = try container.decode([HeadInfo].self, forKey: .head) self = .head(headData) } else if container.contains(.row) { let rowData = try container.decode([SchoolsInfo].self, forKey: .row) self = .row(rowData) } else { throw DecodingError.dataCorruptedError( forKey: CodingKeys.head, in: container, debugDescription: "无法识别的schoolInfo元素类型" ) } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .head(let head): try container.encode(head, forKey: .head) case .row(let row): try container.encode(row, forKey: .row) } } private enum CodingKeys: String, CodingKey { case head, row } }
- 修改根结构体:
struct SchoolResponse: Codable { let schoolInfo: [SchoolInfoElement] } // 保留原有的学校信息结构体,遵循Swift大驼峰命名规范 struct SchoolsInfo: Codable { var schoolName: String var schoolCode: String var officeCode: String private enum CodingKeys: String, CodingKey { case schoolName = "SCHUL_NM" case schoolCode = "SD_SCHUL_CODE" case officeCode = "ATPT_OFCDC_SC_CODE" } }
- 更新ViewModel中的解码逻辑:
do { let decodedResponse = try JSONDecoder().decode(SchoolResponse.self, from: responseData) // 过滤出包含学校数据的row元素 let schoolData = decodedResponse.schoolInfo.compactMap { element -> [SchoolsInfo]? in guard case .row(let schools) = element else { return nil } return schools }.flatMap { $0 } self.schoolAddress = schoolData print("解码成功:\(schoolData)") // 若需传递给代理,建议调整schoolData类型为PublishSubject<[SchoolsInfo]> // self.delegate?.schoolData.onNext(schoolData) } catch let DecodingError.keyNotFound(key, context) { print("缺失键 '\(key)':", context.debugDescription) print("编码路径:", context.codingPath) } catch { print("解码失败:", error) }
方案二:使用可选字段(快速修复)
如果不需要处理head中的元数据,可以给schoolRow结构体添加可选的head和row字段,跳过无row的元素。
修改结构体:
struct SchoolResponse: Codable { let schoolInfo: [SchoolRow] } struct SchoolRow: Codable { let head: [Any]? // 不需要元数据时可简化为[Any]? let row: [SchoolsInfo]? } struct SchoolsInfo: Codable { var schoolName: String var schoolCode: String var officeCode: String private enum CodingKeys: String, CodingKey { case schoolName = "SCHUL_NM" case schoolCode = "SD_SCHUL_CODE" case officeCode = "ATPT_OFCDC_SC_CODE" } }
更新解码逻辑:
do { let decoded = try JSONDecoder().decode(SchoolResponse.self, from: responseData).schoolInfo // 提取非空的row数据 let schoolData = decoded.compactMap { $0.row }.flatMap { $0 } self.schoolAddress = schoolData print("解码成功:\(schoolData)") } catch { print("解码失败:", error) }
注意事项
- Swift命名规范中,结构体、枚举等类型名应采用大驼峰命名(如
SchoolResponse而非schoolResponse),提升代码可读性。 - 若需处理API返回的状态码(如
INFO-000),可通过枚举的.headcase提取ResultInfo进行判断。
内容的提问来源于stack exchange,提问作者Hees
相关产品推荐
相关产品推荐

