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

Swift中JSONDecoder解析API数据出现keyNotFound错误解决问询

解决JSONDecoder keyNotFound错误、null值处理及ViewModel属性访问问题

1. 修复keyNotFound错误

问题根源

去掉decoder.keyDecodingStrategy = .convertFromSnakeCase后,解码器会严格匹配结构体属性名与JSON中的key(比如JSON里的FIRST_NAME和结构体的firstName完全不匹配),直接触发keyNotFound错误。

两种可行修复方案

方案一:手动配置CodingKeys映射

直接在结构体里声明CodingKeys,明确对应JSON的key:

struct Person: Codable {
    var firstName: String
    var midName: String? // 先标记为可选类型,处理null值
    var lastName: String

    enum CodingKeys: String, CodingKey {
        case firstName = "FIRST_NAME"
        case midName = "MID_NAME"
        case lastName = "LAST_NAME"
    }
}

struct CustomerResponse: Codable {
    var customers: [Person]
    // 如果JSON顶级key是其他命名,比如"CUSTOMERS",同样要加CodingKeys映射
}

方案二:重新启用蛇形转驼峰策略

如果JSON的key是蛇形命名(如FIRST_NAME),启用.convertFromSnakeCase后,解码器会自动将全大写蛇形key转为驼峰属性名,只需保证结构体属性名是对应驼峰格式:

struct Person: Codable {
    var firstName: String
    var midName: String?
    var lastName: String
}

struct CustomerResponse: Codable {
    var customers: [Person]
}

// 解码时重新启用策略
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

2. 处理JSON中的null值

JSON中可能为null的字段(比如Vanessa的MID_NAME),必须将结构体对应属性声明为可选类型,解码器遇到null时会自动赋值为nil,避免崩溃:

// 错误写法:无法解码null,会崩溃
// var midName: String

// 正确写法:接收null值
var midName: String?

如果需要给null字段设置默认值(比如空字符串),可以在结构体里加计算属性:

struct Person: Codable {
    var firstName: String
    var midName: String?
    var lastName: String

    // 带默认值的安全访问属性
    var safeMidName: String {
        midName ?? ""
    }
}

3. 实现ViewModel访问Person属性

创建CustomerListViewModel,内部持有Person数组,封装属性访问逻辑,方便UI层调用:

class CustomerListViewModel {
    private var customers: [Person] = []

    // 初始化时传入API解码得到的客户数组
    init(customers: [Person]) {
        self.customers = customers
    }

    // 获取客户总数
    var customerCount: Int {
        customers.count
    }

    // 根据索引获取单个客户(或直接返回属性)
    func customer(at index: Int) -> Person? {
        guard index >= 0 && index < customers.count else { return nil }
        return customers[index]
    }

    // 封装单个属性的访问方法,避免UI层直接依赖Person结构体
    func firstName(for index: Int) -> String? {
        customer(at: index)?.firstName
    }

    func safeMidName(for index: Int) -> String {
        customer(at: index)?.safeMidName ?? ""
    }
}

在Webservice中整合解码与ViewModel初始化

class Webservice {
    func fetchCustomers(completion: @escaping (Result<CustomerListViewModel, Error>) -> Void) {
        guard let url = URL(string: "你的API地址") else {
            completion(.failure(NSError(domain: "无效URL", code: -1, userInfo: nil)))
            return
        }

        URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error {
                completion(.failure(error))
                return
            }

            guard let data = data else {
                completion(.failure(NSError(domain: "未获取到数据", code: -2, userInfo: nil)))
                return
            }

            do {
                let decoder = JSONDecoder()
                // 根据你选择的方案,启用蛇形转驼峰或使用CodingKeys
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                let response = try decoder.decode(CustomerResponse.self, from: data)
                let viewModel = CustomerListViewModel(customers: response.customers)
                completion(.success(viewModel))
            } catch {
                completion(.failure(error))
                print("解码错误:\(error)")
            }
        }.resume()
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 08:10:28