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

Swift Decodable:如何避免泛型处理多类型val字段?

解决JSON混合类型字段的解码问题:无需泛型,仅在Value层级处理

嘿,这个问题我之前也碰到过!不用泛型也能完美解决,核心思路是给val字段定义一个自定义的可解码枚举,把两种可能的类型都封装进去,这样就能把类型处理完全限制在Value层级,不会影响上层的DocumentDraft或者你的API调用链。

步骤1:定义封装两种类型的枚举

首先创建一个ValueContent枚举,它实现Decodable协议,负责处理String和[String]两种类型的解码:

enum ValueContent: Decodable {
    case single(String)
    case multiple([String])
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        // 先尝试解码成单个字符串
        if let singleValue = try? container.decode(String.self) {
            self = .single(singleValue)
        } 
        // 失败的话尝试解码成字符串数组
        else if let multipleValues = try? container.decode([String].self) {
            self = .multiple(multipleValues)
        } 
        // 两种都不符合就抛出解码错误
        else {
            throw DecodingError.typeMismatch(
                ValueContent.self,
                DecodingError.Context(
                    codingPath: decoder.codingPath,
                    debugDescription: "Expected either a String or an array of Strings for 'val' field"
                )
            )
        }
    }
}

步骤2:修改Value结构体,替换泛型为枚举

把原来的泛型Value<V>改成普通的Value结构体,用上面的枚举来定义val字段:

struct Value: Decodable {
    let key: String
    let val: ValueContent?
}

这样一来,你的上层DocumentDraft结构完全不需要改动,保持原来的简洁性:

struct Schema: Decodable {
    // 保持你原来的Schema定义不变
}

struct DocumentDraft: Decodable {
    let uuid: String
    let schema: Schema
    let url: String
    let values: [Value]
    let successors: [String]
    let predecessors: [String]
}

步骤3:使用时便捷访问值

为了使用起来更顺手,你可以给ValueContent加几个扩展属性,方便快速获取对应类型的值:

extension ValueContent {
    // 获取单个字符串值(仅当val是String时有效)
    var stringValue: String? {
        switch self {
        case .single(let str): return str
        case .multiple: return nil
        }
    }
    
    // 获取字符串数组值(仅当val是[String]时有效)
    var arrayValue: [String]? {
        switch self {
        case .single: return nil
        case .multiple(let arr): return arr
        }
    }
    
    // 统一转成数组:单个字符串会被包装成单元素数组
    var asArray: [String] {
        switch self {
        case .single(let str): return [str]
        case .multiple(let arr): return arr
        }
    }
}

比如你遍历values数组时,可以这样处理:

do {
    let decoder = JSONDecoder()
    let draft = try decoder.decode(DocumentDraft.self, from: jsonData)
    
    for value in draft.values {
        guard let content = value.val else { continue }
        
        print("Key: \(value.key)")
        if let singleStr = content.stringValue {
            print("Single value: \(singleStr)")
        } else if let arr = content.arrayValue {
            print("Multiple values: \(arr.joined(separator: ", "))")
        }
        
        // 或者统一用数组处理,不用区分类型
        print("All values as array: \(content.asArray)")
    }
} catch {
    print("Decoding error: \(error.localizedDescription)")
}

为什么这个方案更好?

  • 不破坏上层结构:完全把类型处理限制在Value层级,DocumentDraft、API调用链都不需要改,保持原来的简洁性。
  • 扩展性强:如果以后val字段新增了其他类型(比如数字),只需要给ValueContent枚举加新的case就行。
  • 类型安全:通过枚举明确约束了val的可能类型,避免了使用Any带来的类型不安全问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 21:27:45