如何在Swift模型中使用协议并实现异构JSON数组的正确反序列化
场景说明
现有JSON结构示例如下:
"sittingFurniture":[ { "sittingObjectType": "chair", "fabric": "textile" }, { "sittingObjectType":"bed", "height": 70 }, ... ]
最初编写的Swift代码如下:
protocol SittingObject { var type: SittingObjectType } public enum SittingObjectType: Codable, Equatable { case chair case sofa case bed } struct FancyChair: SittingObject, Codable, Equatable { let fabric: String let type: SittingObjectType = .chair } struct FancyBed: SittingObject, Codable, Equatable{ let height: Int let type: SittingObjectType = .bed } struct FurnitureList: Codable, Equatable { let sittingFurniture: [SittingObject] //ERROR }
编译报错信息:
Protocol 'SittingObject' can only be used as a generic constraint because it has Self or associated type requirements
中文释义:协议SittingObject只能用作泛型约束,因为它包含Self或关联类型要求
最优反序列化方案
报错核心原因:Swift中包含Self或关联类型要求的协议不能直接作为集合的元素类型使用,且协议本身无法直接遵从Codable协议完成序列化/反序列化。
该场景下最符合Swift语言规范、类型最安全的方案是将原本的SittingObject协议替换为带关联值的枚举,统一包装所有坐具类型,手动实现枚举的解码逻辑,完整实现代码如下:
// 坐具类型枚举保持不变,注意加上String绑定适配JSON字符串值 public enum SittingObjectType: String, Codable, Equatable { case chair case sofa case bed } // 不同坐具的具体结构保持不变 struct FancyChair: Codable, Equatable { let fabric: String } struct FancyBed: Codable, Equatable{ let height: Int } struct FancySofa: Codable, Equatable { let seatCount: Int // 示例字段,可根据实际业务调整 } // 用带关联值的枚举统一承载所有坐具类型 enum SittingObject: Codable, Equatable { case chair(FancyChair) case bed(FancyBed) case sofa(FancySofa) // 手动实现解码逻辑 init(from decoder: Decoder) throws { // 先解码类型字段判断坐具类型 let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(SittingObjectType.self, forKey: .sittingObjectType) switch type { case .chair: // 类型匹配时解码对应结构的所有字段 let chair = try FancyChair(from: decoder) self = .chair(chair) case .bed: let bed = try FancyBed(from: decoder) self = .bed(bed) case .sofa: let sofa = try FancySofa(from: decoder) self = .sofa(sofa) } } // 如需序列化功能可补充encode实现 func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .chair(let chair): try container.encode(SittingObjectType.chair, forKey: .sittingObjectType) try chair.encode(to: encoder) case .bed(let bed): try container.encode(SittingObjectType.bed, forKey: .sittingObjectType) try bed.encode(to: encoder) case .sofa(let sofa): try container.encode(SittingObjectType.sofa, forKey: .sittingObjectType) try sofa.encode(to: encoder) } } enum CodingKeys: String, CodingKey { case sittingObjectType } } // 外层结构直接使用枚举数组即可,编译正常 struct FurnitureList: Codable, Equatable { let sittingFurniture: [SittingObject] }
用法示例
需要访问具体坐具的属性时,直接通过switch匹配枚举提取关联值即可:
let furnitureList = try JSONDecoder().decode(FurnitureList.self, from: jsonData) for item in furnitureList.sittingFurniture { switch item { case .chair(let chair): print("椅子的面料:\(chair.fabric)") case .bed(let bed): print("床的高度:\(bed.height)") case .sofa(let sofa): print("沙发的座位数:\(sofa.seatCount)") } }
该方案的优势:
- 完全类型安全,编译器会自动校验所有属性的访问逻辑
- 解码逻辑清晰,可自定义不同类型的解码容错规则
- 不需要额外的类型转换操作,代码维护成本低
内容的提问来源于stack exchange,提问作者Vodenjak
相关产品推荐
相关产品推荐

