如何正确重新编码时而为对象时而为数组的坐标类型?
保留坐标原始编码格式的实现方案
针对你需要将坐标按原始解码时的格式(对象/数组)重新编码的需求,结合你提到的API新旧格式并存、编码规则明确的场景,这里提供两种可行方案:
方案一:记录原始编码类型(轻量首选)
在Coordinates结构体中新增私有属性记录解码时识别到的原始格式,编码时根据该值匹配对应逻辑,这是改动最小、最直接的实现方式。
具体实现
- 给
Coordinates结构体添加格式标记属性:
struct Coordinates<Element: Codable> { var x: Element var y: Element? var z: Element // 私有枚举标记原始编码格式 private enum OriginalFormat { case object case array } private var originalFormat: OriginalFormat }
- 修改解码逻辑,成功解码时同步设置格式标记,新增编码逻辑匹配原始格式:
extension Coordinates : Codable { private enum CodingKeys : String, CodingKey { case x case y case z } init(from decoder : Decoder) throws { if let container = try? decoder.container(keyedBy: CodingKeys.self) { self.x = try container.decode(Element.self, forKey: .x) self.y = try container.decodeIfPresent(Element.self, forKey: .y) self.z = try container.decode(Element.self, forKey: .z) self.originalFormat = .object } else if var container = try? decoder.unkeyedContainer() { self.x = try container.decode(Element.self) self.z = try container.decode(Element.self) if let z = try? container.decode(Element.self) { self.y = self.z self.z = z } self.originalFormat = .array } else { preconditionFailure("Cannot decode coordinates") } } // 根据原始格式选择编码方式 func encode(to encoder: Encoder) throws { switch originalFormat { case .object: var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(x, forKey: .x) try container.encodeIfPresent(y, forKey: .y) try container.encode(z, forKey: .z) case .array: var container = encoder.unkeyedContainer() try container.encode(x) // 兼容数组的2元素/3元素场景 if let y = y { try container.encode(y) try container.encode(z) } else { try container.encode(z) } } } }
这个方案的优势:
- 无需拆分现有结构体,代码改动量极小
- 逻辑直观,完全匹配“解码记格式、编码复用格式”的需求
- 适配你当前API新旧格式并存的场景
方案二:协议+多结构体实现(类型化区分)
如果希望从类型层面直接区分两种格式的坐标,可以定义Coordinates协议,分别实现对象格式和数组格式的结构体。
具体实现
- 定义通用协议:
protocol Coordinates: Codable { associatedtype Element: Codable var x: Element { get set } var y: Element? { get set } var z: Element { get set } }
- 实现两种格式的结构体:
// 对象格式坐标 struct StructuredCoordinates<Element: Codable>: Coordinates { var x: Element var y: Element? var z: Element private enum CodingKeys: String, CodingKey { case x, y, z } } // 数组格式坐标 struct ArrayCoordinates<Element: Codable>: Coordinates { var x: Element var y: Element? var z: Element init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() self.x = try container.decode(Element.self) self.z = try container.decode(Element.self) if let z = try? container.decode(Element.self) { self.y = self.z self.z = z } } func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(x) if let y = y { try container.encode(y) try container.encode(z) } else { try container.encode(z) } } }
- 辅助解码方法,根据格式实例化对应结构体:
func decodeCoordinates<Element: Codable>(from data: Data) throws -> any Coordinates where Element == Element.Element { do { return try JSONDecoder().decode(StructuredCoordinates<Element>.self, from: data) } catch { return try JSONDecoder().decode(ArrayCoordinates<Element>.self, from: data) } }
这个方案的优势:
- 类型语义清晰,不同格式的坐标是独立类型
- 编码逻辑完全隔离,各自维护自身规则
- 适合未来格式可能进一步扩展的场景
方案选择建议
结合你的场景(事先知晓编码格式、API新旧格式并存),**方案一(记录原始类型)**是最优选择,它不需要改动现有结构体的使用方式,代码成本极低,完全满足需求。如果未来需要对两种格式做更多差异化处理,再考虑切换到方案二。
内容的提问来源于stack exchange,提问作者Ter Maxima
相关产品推荐
相关产品推荐

