You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.20 18:36:03