Swift JSON解析问题:空数组替代nil导致崩溃的解决方法
解决Swift Codable解析空数组为可选对象nil的问题
针对API返回空数组替代nil,但模型定义为单个可选对象导致的解析崩溃问题,提供三种实用解决方案:
方案一:模型内自定义解码逻辑
直接在目标模型中重写init(from decoder: Decoder),针对每个字段单独处理两种情况(单个对象/空数组):
struct FinancialData: Codable { let ebitda: Ebitda? let quickRatio: QuickRatio? enum CodingKeys: String, CodingKey { case ebitda, quickRatio } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 处理ebitda字段:优先解码为单个对象,失败则检查是否为空数组 if let ebitdaObj = try? container.decode(Ebitda.self, forKey: .ebitda) { ebitda = ebitdaObj } else if let emptyArray = try? container.decode([Ebitda].self, forKey: .ebitda), emptyArray.isEmpty { ebitda = nil } else { // 非预期格式(如非空数组),可设为nil或抛出错误,根据业务需求调整 ebitda = nil } // 同理处理quickRatio字段 if let ratioObj = try? container.decode(QuickRatio.self, forKey: .quickRatio) { quickRatio = ratioObj } else if let emptyArray = try? container.decode([QuickRatio].self, forKey: .quickRatio), emptyArray.isEmpty { quickRatio = nil } else { quickRatio = nil } } } // 子模型保持不变 struct Ebitda: Codable { let value: Double let period: String } struct QuickRatio: Codable { let value: Double let period: String }
优缺点:无需额外定义类型,但字段较多时代码重复度高,仅适合少量字段的场景。
方案二:通用包装类型
定义一个实现Codable的通用结构体,统一处理「单个对象/空数组」的解码逻辑,复用性更强:
// 通用包装类型:支持将单个对象解码为T?,空数组解码为nil struct OptionalSingleOrEmptyArray<T: Codable>: Codable { let value: T? init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { // 尝试解码为单个对象 value = try container.decode(T.self) } catch { // 解码失败则尝试解析数组 let array = try container.decode([T].self) // 空数组返回nil;若API可能返回非空数组,可改为取第一个元素或抛出错误 value = array.isEmpty ? nil : array.first } } // 编码时反向处理:nil转为空数组,非nil转为单个对象 func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() if let value = value { try container.encode(value) } else { try container.encode([]) } } } // 模型中使用包装类型,可加计算属性简化外部访问 struct FinancialData: Codable { let ebitda: OptionalSingleOrEmptyArray<Ebitda> let quickRatio: OptionalSingleOrEmptyArray<QuickRatio> // 对外暴露原生可选类型的计算属性 var ebitdaValue: Ebitda? { ebitda.value } var quickRatioValue: QuickRatio? { quickRatio.value } }
优缺点:一次定义多处复用,适合多字段场景,但需通过value属性访问实际对象,稍显繁琐。
方案三:属性包装器(推荐)
使用Swift属性包装器,实现最简洁的语法,对外完全兼容原生可选类型的用法:
@propertyWrapper struct SingleOrEmptyArray<T: Codable>: Codable { var wrappedValue: T? init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { wrappedValue = try container.decode(T.self) } catch { let array = try container.decode([T].self) wrappedValue = array.isEmpty ? nil : array.first } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() if let value = wrappedValue { try container.encode(value) } else { try container.encode([]) } } } // 让属性包装器支持Equatable(可选,根据模型需求添加) extension SingleOrEmptyArray: Equatable where T: Equatable {} // 模型中直接使用属性包装器,用法与原生可选类型一致 struct FinancialData: Codable { @SingleOrEmptyArray var ebitda: Ebitda? @SingleOrEmptyArray var quickRatio: QuickRatio? }
使用时直接访问ebitda即可,完全无需关心底层的解码逻辑,语法最简洁,复用性最强,是优先推荐的方案。
内容的提问来源于stack exchange,提问作者Micsa Dan
相关产品推荐
相关产品推荐

