Swift Codable解析含可选互斥字段JSON返回nil问题求解
问题原因
- 核心错误:
ShortLinkData自定义解码方法中,result字段使用decode(_:forKey:)方法解析,该方法要求目标字段必须存在于JSON中,否则直接抛出解码错误。当接口返回失败响应时JSON无result字段,解码直接失败,被外层try?转换为nil,因此始终拿不到解析结果。 - 潜在冲突:自定义的结果结构体命名为
Result,和Swift标准库中的Result枚举重名,容易引发类型歧义错误。 - 冗余代码:两个模型都手写了全量
init(from decoder:)实现,只要CodingKeys配置正确,Codable会自动生成正确的解码实现,多余手写代码反而增加出错概率。
修正方案
方案1:最小改动适配
只需要调整result字段的解码方式,修正类型命名即可,不需要改动整体结构:
struct ShortLinkData: Codable { let ok: Bool let result: ShortLinkResult? let errorCode: Int? let error: String? private enum CodingKeys : String, CodingKey { case ok, result case errorCode = "error_code" case error } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) ok = try container.decode(Bool.self, forKey: .ok) // 改为decodeIfPresent,字段不存在时返回nil而非抛错 result = try container.decodeIfPresent(ShortLinkResult.self, forKey: .result) errorCode = try container.decodeIfPresent(Int.self, forKey: .errorCode) error = try container.decodeIfPresent(String.self, forKey: .error) } } // 重命名避免和系统Result类型冲突 struct ShortLinkResult: Codable { let code, shortLink: String let fullShortLink: String let shortLink2: String let fullShortLink2: String let shortLink3: String let fullShortLink3: String let shareLink: String let fullShareLink: String let originalLink: String enum CodingKeys: String, CodingKey { case code case shortLink = "short_link" case fullShortLink = "full_short_link" case shortLink2 case fullShortLink2 = "full_short_link2" case shortLink3 case fullShortLink3 = "full_short_link3" case shareLink = "share_link" case fullShareLink = "full_share_link" case originalLink = "original_link" } // 无需手写init,Codable自动生成的实现可正常工作 }
解析时不要用强制解包,避免异常崩溃:
let str = String(decoding: data, as: UTF8.self) print(str) do { let shortURL = try JSONDecoder().decode(ShortLinkData.self, from: data) if shortURL.ok { // 处理成功逻辑,直接取result字段即可 print("短链接地址:\(shortURL.result?.fullShortLink ?? "")") } else { // 处理失败逻辑,取错误码和错误信息 print("请求失败:\(shortURL.error ?? ""),错误码:\(shortURL.errorCode ?? -1)") } } catch { print("解析失败:\(error.localizedDescription)") }
方案2:枚举类型严谨实现(推荐)
由于成功、失败返回的字段完全互斥,可以使用带关联值的枚举实现模型,从类型系统层面避免同时拿到结果和错误的异常状态:
enum ShortLinkResponse: Codable { case success(result: ShortLinkResult) case failure(errorCode: Int, errorMsg: String) private enum CodingKeys: String, CodingKey { case ok, result, error_code, error } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let isSuccess = try container.decode(Bool.self, forKey: .ok) if isSuccess { let result = try container.decode(ShortLinkResult.self, forKey: .result) self = .success(result: result) } else { let code = try container.decode(Int.self, forKey: .error_code) let msg = try container.decode(String.self, forKey: .error) self = .failure(errorCode: code, errorMsg: msg) } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .success(let result): try container.encode(true, forKey: .ok) try container.encode(result, forKey: .result) case .failure(let code, let msg): try container.encode(false, forKey: .ok) try container.encode(code, forKey: .error_code) try container.encode(msg, forKey: .error) } } } // ShortLinkResult 结构体同方案1 struct ShortLinkResult: Codable { let code, shortLink: String let fullShortLink: String let shortLink2: String let fullShortLink2: String let shortLink3: String let fullShortLink3: String let shareLink: String let fullShareLink: String let originalLink: String enum CodingKeys: String, CodingKey { case code case shortLink = "short_link" case fullShortLink = "full_short_link" case shortLink2 case fullShortLink2 = "full_short_link2" case shortLink3 case fullShortLink3 = "full_short_link3" case shareLink = "share_link" case fullShareLink = "full_share_link" case originalLink = "original_link" } }
使用时直接通过switch分支处理,不需要解包可选值:
do { let response = try JSONDecoder().decode(ShortLinkResponse.self, from: data) switch response { case .success(let result): print("解析成功,短链接:\(result.fullShortLink)") case .failure(let code, let msg): print("请求失败,错误码\(code):\(msg)") } } catch { print("解析失败:\(error.localizedDescription)") }
内容的提问来源于stack exchange,提问作者mars_dev
相关产品推荐
相关产品推荐

