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

Swift中解析含不同类型对象数组的特定JSON问题咨询

混合类型JSON数组的解码方案

不建议使用合并Header和Detail所有字段、非共通字段设为可选的聚合结构。这种方案会导致后续使用时需要大量判断可选值,类型安全度低,30个字段的规模下维护成本会非常高。更合理的方案是通过自定义Decodable实现分类型解析,具体实现步骤如下:

步骤1:定义独立的Header和Detail结构体

两个结构体分别对应头结构和明细结构,各自声明自身需要的字段即可:

struct Header: Decodable {
    let rectype: Int
    let recname: String
    let companyname: String
    let companyId: String
    // 其他头结构专属字段
}

struct Detail: Decodable {
    let rectype: Int
    let recname: String
    let locationId: String
    let shelvingUnits: Int
    // 其他明细结构专属字段
}

步骤2:定义枚举承载两种类型的解析结果

用带关联值的枚举作为中间解析类型,根据rectype字段自动判断解析为对应类型:

enum ResultEntity: Decodable {
    case header(Header)
    case detail(Detail)
    
    enum CodingKeys: String, CodingKey {
        case rectype
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let rectype = try container.decode(Int.self, forKey: .rectype)
        switch rectype {
        case 1:
            self = .header(try Header(from: decoder))
        case 2:
            self = .detail(try Detail(from: decoder))
        default:
            throw DecodingError.dataCorruptedError(
                forKey: .rectype,
                in: container,
                debugDescription: "未识别的记录类型:\(rectype)"
            )
        }
    }
}

步骤3:自定义APIResponse的解码逻辑

直接将数组拆分出头和明细两个明确类型的属性,上层使用时无需再处理数组索引:

struct APIResponse: Decodable {
    let resultStatus: Int
    let header: Header
    let details: [Detail]
    
    enum CodingKeys: String, CodingKey {
        case resultStatus, resultEntities
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // 解析状态码
        resultStatus = try container.decode(Int.self, forKey: .resultStatus)
        // 解析实体数组
        let entities = try container.decode([ResultEntity].self, forKey: .resultEntities)
        
        // 校验并提取头结构
        guard let first = entities.first, case .header(let header) = first else {
            throw DecodingError.dataCorruptedError(
                forKey: .resultEntities,
                in: container,
                debugDescription: "实体数组首位必须为头结构"
            )
        }
        self.header = header
        
        // 提取所有明细结构,若要求非头位置必须为明细可替换为遍历判断抛错
        details = entities.dropFirst().compactMap { entity in
            guard case .detail(let detail) = entity else { return nil }
            return detail
        }
    }
}

该方案的优势:

  • 类型完全安全:Header和Detail的专属字段无需设为可选,后续使用时不需要额外判空
  • 扩展性强:后续如果新增其他类型的记录,仅需要在ResultEntity枚举中新增对应case即可
  • 使用成本低:对外暴露的属性清晰明确,上层调用直接取header和details即可,不需要感知底层数组的索引规则

内容的提问来源于stack exchange,提问作者chornbe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 07:15:01