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

如何通过Codable协议结合JSONDecoder解析多结构体JSON数据?

解决Swift中遵循Codable协议的多结构体解码问题

首先得明确一个关键点:你不能直接把JSON解码到Animal协议类型,因为Codable需要具体的类型来实例化对象,而协议本身是抽象的,没有对应的初始化逻辑。不过我们可以通过两种常见的方式来解决这个问题,下面一步步来讲解。

方法一:用枚举作为包装类型(推荐)

这种方法最清晰可靠,我们可以创建一个枚举来包裹所有遵循Animal的结构体,让这个枚举实现Codable,并在解码时判断具体的类型。

步骤1:给JSON添加类型标识(可选但推荐)

为了明确区分是Dog还是Cat,最好在每个JSON payload里加一个类型字段,比如"kind": "dog"或者"kind": "cat"。修改后的JSON如下:

Dog的JSON:

{
  "kind": "dog",
  "name": "fleabag",
  "age": 3,
  "type": "big"
}

Cat的JSON:

{
  "kind": "cat",
  "name": "felix",
  "age": 2,
  "color": "black"
}

步骤2:实现包装枚举

protocol Animal: Codable {
    var name: String { get }
    var age: Int { get }
}

struct Dog: Animal {
    let name: String
    let age: Int
    let type: String
}

struct Cat: Animal {
    let name: String
    let age: Int
    let color: String
}

// 包装枚举,处理解码逻辑
enum AnimalWrapper: Codable {
    case dog(Dog)
    case cat(Cat)
    
    // 定义编码解码的key
    private enum CodingKeys: String, CodingKey {
        case kind
    }
    
    // 枚举类型,对应JSON里的kind字段值
    private enum AnimalType: String, Codable {
        case dog, cat
    }
    
    // 自定义解码逻辑
    init(from decoder: Decoder) throws {
        // 先解析kind字段判断类型
        let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
        let animalType = try keyedContainer.decode(AnimalType.self, forKey: .kind)
        
        // 再解析对应的结构体
        let singleContainer = try decoder.singleValueContainer()
        switch animalType {
        case .dog:
            self = .dog(try singleContainer.decode(Dog.self))
        case .cat:
            self = .cat(try singleContainer.decode(Cat.self))
        }
    }
    
    // 自定义编码逻辑(如果需要编码的话)
    func encode(to encoder: Encoder) throws {
        var keyedContainer = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .dog(let dog):
            try keyedContainer.encode(AnimalType.dog, forKey: .kind)
            try dog.encode(to: encoder)
        case .cat(let cat):
            try keyedContainer.encode(AnimalType.cat, forKey: .kind)
            try cat.encode(to: encoder)
        }
    }
}

步骤3:使用JSONDecoder解码

// 解码Dog的示例
let dogJsonData = """
{
    "kind": "dog",
    "name": "fleabag",
    "age": 3,
    "type": "big"
}
""".data(using: .utf8)!

do {
    let animalWrapper = try JSONDecoder().decode(AnimalWrapper.self, from: dogJsonData)
    switch animalWrapper {
    case .dog(let dog):
        print("解码出Dog:名字\(dog.name),年龄\(dog.age),品种\(dog.type)")
    case .cat(let cat):
        print("解码出Cat:名字\(cat.name),年龄\(cat.age),颜色\(cat.color)")
    }
} catch {
    print("解码失败:\(error.localizedDescription)")
}

// 解码Cat的示例
let catJsonData = """
{
    "kind": "cat",
    "name": "felix",
    "age": 2,
    "color": "black"
}
""".data(using: .utf8)!

do {
    let animalWrapper = try JSONDecoder().decode(AnimalWrapper.self, from: catJsonData)
    switch animalWrapper {
    case .dog(let dog):
        print("解码出Dog:名字\(dog.name),年龄\(dog.age),品种\(dog.type)")
    case .cat(let cat):
        print("解码出Cat:名字\(cat.name),年龄\(cat.age),颜色\(cat.color)")
    }
} catch {
    print("解码失败:\(error.localizedDescription)")
}

方法二:通过字段判断类型(无需添加kind字段)

如果不想修改原有的JSON,也可以通过判断JSON中是否存在type(Dog的专属字段)或color(Cat的专属字段)来区分类型。这种方法需要借助动态CodingKey:

实现代码

// 定义动态CodingKey,用于检查字段是否存在
struct DynamicCodingKey: CodingKey {
    let stringValue: String
    init?(stringValue: String) {
        self.stringValue = stringValue
    }
    var intValue: Int? { nil }
    init?(intValue: Int) { nil }
}

// 修改AnimalWrapper的解码逻辑
enum AnimalWrapper: Codable {
    case dog(Dog)
    case cat(Cat)
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: DynamicCodingKey.self)
        
        // 检查是否有type字段,是Dog;否则是Cat
        if container.contains(DynamicCodingKey(stringValue: "type")!) {
            self = .dog(try decoder.decode(Dog.self))
        } else if container.contains(DynamicCodingKey(stringValue: "color")!) {
            self = .cat(try decoder.decode(Cat.self))
        } else {
            throw DecodingError.dataCorrupted(
                DecodingError.Context(
                    codingPath: [],
                    debugDescription: "无法识别的动物类型"
                )
            )
        }
    }
    
    // 编码逻辑和方法一一致,这里省略
    func encode(to encoder: Encoder) throws {
        // ... 同方法一的编码逻辑
    }
}

这种方法的缺点是扩展性差,如果以后新增其他遵循Animal的结构体,需要修改判断逻辑,不如方法一清晰。

总结

推荐使用方法一,通过添加类型标识字段+枚举包装的方式,逻辑清晰且扩展性好,能轻松应对后续新增的Animal类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:02:01