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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 09:54:04