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
相关产品推荐
相关产品推荐

