如何使用Swift Codable高效解析含冗余嵌套结构的API返回JSON
针对这类固定规则的多层嵌套JSON解析,最高效的方式是抽象通用的嵌套包装层,无需重复编写CodingKeys,也不需要手动修改原始JSON,完全保留Codable的类型安全特性。
方案实现步骤
1. 预定义通用复用包装结构体
这部分代码只需要写一次,所有同格式的接口都可以复用:
// 用于匹配最外层包裹data的结构 struct OuterDataWrapper<T: Codable>: Codable { let data: [InnerDataWrapper<T>] } // 用于匹配数组元素层包裹data的结构 struct InnerDataWrapper<T: Codable>: Codable { let data: T } // 用于匹配资源层的固定结构:id、attributes、relationships struct Resource<Attr: Codable, Rel: Codable>: Codable { let id: Int let attributes: Attr let relationships: Rel } // 空占位模型,用于没有关联关系的资源 struct EmptyRel: Codable {}
2. 定义对应业务字段的轻量结构体
只需要声明属性,完全依赖Codable自动合成解析逻辑,无需手动写任何解码代码:
// User对应的attributes字段结构 struct UserAttr: Codable { let name: String } // User对应的relationships字段结构 struct UserRel: Codable { let photo: InnerDataWrapper<Resource<PhotoAttr, EmptyRel>> } // Photo对应的attributes字段结构 struct PhotoAttr: Codable { let url: String }
3. 直接解析并转换为原有业务模型
不需要修改你已经定义好的User和Photo结构体,解码后做一层轻量转换即可:
func parseUsers(from data: Data) throws -> [User] { let decoder = JSONDecoder() // 直接用通用包装解码原始数据 let result = try decoder.decode(OuterDataWrapper<Resource<UserAttr, UserRel>>.self, from: data) // 转换为原有业务模型 return result.data.map { item in let userRes = item.data let photoRes = userRes.relationships.photo.data return User( id: userRes.id, name: userRes.attributes.name, photo: Photo(id: photoRes.id, url: photoRes.attributes.url) ) } }
方案优势
- 通用包装层完全复用,后续新增同格式接口不需要重复编写嵌套解析逻辑
- 全链路类型安全,字段缺失、类型不匹配都会触发明确的解码错误,远高于手动修改JSON的容错性
- 业务层代码和解析逻辑解耦,后续如果后端调整嵌套规则,只需要修改通用包装层即可,不需要改动业务模型代码
进阶简化方案
如果项目中这类嵌套场景非常多,还可以通过自定义属性包装器进一步简化代码,直接在业务模型上标记嵌套规则即可,连轻量的Attr、Rel结构体都不需要定义:
// 自定义通用CodingKey用于动态匹配字段 struct AnyCodingKey: CodingKey { let stringValue: String let intValue: Int? init?(stringValue: String) { self.stringValue = stringValue self.intValue = nil } init?(intValue: Int) { self.intValue = intValue self.stringValue = "\(intValue)" } } // 自定义属性包装器自动处理data -> attributes的嵌套路径 @propertyWrapper struct Attribute<T: Codable>: Codable { let wrappedValue: T init(from decoder: Decoder) throws { let rootContainer = try decoder.container(keyedBy: AnyCodingKey.self) let dataContainer = try rootContainer.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue: "data")!) let attrContainer = try dataContainer.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue: "attributes")!) let key = AnyCodingKey(stringValue: decoder.codingPath.last!.stringValue)! wrappedValue = try attrContainer.decode(T.self, forKey: key) } }
使用时直接标记业务属性即可:
struct User: Codable { let id: Int @Attribute var name: String // 关联关系也可以用同样的逻辑自定义属性包装器处理 }
内容的提问来源于stack exchange,提问作者LinusG.
相关产品推荐
相关产品推荐

