You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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数组中的两种元素类型,结构清晰,避免可选值的模糊性。

  1. 定义元数据相关结构体:
// 处理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"
    }
}
  1. 定义枚举处理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
    }
}
  1. 修改根结构体:
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"
    }
}
  1. 更新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),可通过枚举的.head case提取ResultInfo进行判断。

内容的提问来源于stack exchange,提问作者Hees

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 12:15:25