向启用JWT鉴权的API发起GET请求时JSON数据解码失败
- 对接开启JWT鉴权的项目列表GET接口时,返回的字节数据无法解码为预期的Project对象数组
- 业务逻辑:用户登录后点击按钮,携带接口返回的JWT令牌发起请求拉取项目数据,用于跳转项目列表页面
- 现有网络请求实现:
func getUserProject(token: String, completion: @escaping (Result<[Project], NetworkError>) -> Void) { guard let url = URL(string: "https://api.t.io/api/manage/user/projects?archived=false&logic=and&page=0&size=10") else { completion(.failure(.invalidURL)) print("unable to connect URL🆘") return } var request = URLRequest(url: url) request.httpMethod = "GET" request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let task = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data, error == nil else { completion(.failure(.noData)) print("Bad🥺") return } guard let Projects = try? JSONDecoder().decode([Project].self, from: data) else { completion(.failure(.decodingError)) print(data) print("Error Decoding Data🥵") return } completion(.success(Projects)) } task.resume() }
- 运行触发解码错误,错误日志截图:

- 当前定义的Project模型代码:
struct Project: Codable { let createdBy: String let createdDate: EdDate let lastModifiedBy: String let lastModifiedDate: EdDate let id: Int let name: String let customID: JSONNull? let shared: Bool let salt: JSONNull? let isCOMRequestAutomatic: Bool let client: Client let company: Company let projectKeywords: [JSONAny] let projectStartDate, projectEndDate: JSONNull? enum CodingKeys: String, CodingKey { case createdBy, createdDate, lastModifiedBy, lastModifiedDate, id, name, status case imageURL case customID case shared, salt case isCOMRequestAutomatic case client, company, projectKeywords, projectStartDate, projectEndDate } }
先确认接口真实返回结构
当前直接打印data只能看到原始字节,无法确认JSON结构,先把data转成UTF8字符串打印,确认返回格式:// 在拿到data后、解码前加这段代码 if let rawJson = String(data: data, encoding: .utf8) { print("接口原始返回:\(rawJson)") }带分页参数的接口绝大多数不会直接返回数组,一般会带通用响应外层包装,比如返回结构为
{ "code": 200, "msg": "success", "data": { "list": [/* 项目数组 */], "total": 100 } },这种情况直接解码[Project].self必然失败,需要根据真实返回结构编写对应的外层响应模型,从嵌套字段中取项目数组再解码。修正模型CodingKeys和属性的映射关系
当前CodingKeys中枚举了status、imageURL两个键,但是Project结构体中没有定义这两个对应的属性,一旦接口返回这两个字段,会直接触发解码失败;如果接口不返回这两个字段,CodingKeys中声明了非可选的缺失字段也会报错。要么补全这两个字段的属性定义,要么把CodingKeys中多余的键删除,保证所有枚举的CodingKey都有对应属性,所有需要解码的属性都有匹配的CodingKey映射。捕获具体解码错误,不要用
try?吞掉异常
当前用try?强制解包解码结果,一旦失败看不到任何具体错误信息,把解码逻辑替换为do-catch写法,拿到系统抛出的具体错误,可以直接定位到是字段类型不匹配、字段缺失还是嵌套模型解析失败:do { // 注意如果接口有外层包装,这里要解码对应的外层响应模型,不是直接解[Project] let projectList = try JSONDecoder().decode([Project].self, from: data) completion(.success(projectList)) } catch { // 这里会打印精确到字段的错误信息,比如"No value associated with key CodingKeys(stringValue: "status", intValue: nil)" print("解码失败详情:\(error)") completion(.failure(.decodingError)) }检查嵌套类型的Codable实现
确认模型中用到的EdDate、Client、Company、JSONNull、JSONAny所有自定义类型都正确实现了Codable协议,任意一个嵌套类型不满足Codable要求,都会导致整个Project模型解码失败。
内容的提问来源于stack exchange,提问作者Wilson Agene

