使用Codable进行JSON(反)序列化时保留未知键的方法
保留Codable转换中的未知JSON键
要解决Codable转换时丢失未知键的问题,核心思路是在解码阶段捕获并存储未知键值对,编码阶段再将这些键值对写回JSON。以下是具体实现方案:
1. 定义通用的AnyCodable类型
用来处理JSON中任意类型的值(字符串、数字、布尔值、数组、对象等),让未知键的值能被正确编码和解码:
enum AnyCodable: Codable { case string(String) case int(Int) case double(Double) case bool(Bool) case array([AnyCodable]) case object([String: AnyCodable]) case null init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let str = try? container.decode(String.self) { self = .string(str) } else if let int = try? container.decode(Int.self) { self = .int(int) } else if let double = try? container.decode(Double.self) { self = .double(double) } else if let bool = try? container.decode(Bool.self) { self = .bool(bool) } else if let array = try? container.decode([AnyCodable].self) { self = .array(array) } else if let object = try? container.decode([String: AnyCodable].self) { self = .object(object) } else if container.decodeNil() { self = .null } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported JSON type") } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .string(let str): try container.encode(str) case .int(let int): try container.encode(int) case .double(let double): try container.encode(double) case .bool(let bool): try container.encode(bool) case .array(let array): try container.encode(array) case .object(let object): try container.encode(object) case .null: try container.encodeNil() } } }
2. 实现动态CodingKey
用来处理任意字符串形式的JSON键:
struct DynamicCodingKey: CodingKey { let stringValue: String init?(stringValue: String) { self.stringValue = stringValue } let intValue: Int? init?(intValue: Int) { self.intValue = intValue self.stringValue = String(intValue) } }
3. 自定义Codable模型
在模型中添加存储未知键的属性,并自定义解码/编码逻辑:
struct MyModel: Codable { let name: String let age: Int private(set) var unknownKeys: [String: AnyCodable] = [:] // 已知键的CodingKeys enum CodingKeys: String, CodingKey { case name, age } // 自定义解码逻辑,捕获未知键 init(from decoder: Decoder) throws { // 解码已知属性 let knownContainer = try decoder.container(keyedBy: CodingKeys.self) name = try knownContainer.decode(String.self, forKey: .name) age = try knownContainer.decode(Int.self, forKey: .age) // 捕获所有未知键 let allKeysContainer = try decoder.container(keyedBy: DynamicCodingKey.self) for key in allKeysContainer.allKeys { // 跳过已定义的键 guard CodingKeys(rawValue: key.stringValue) == nil else { continue } unknownKeys[key.stringValue] = try allKeysContainer.decode(AnyCodable.self, forKey: key) } } // 自定义编码逻辑,写入未知键 func encode(to encoder: Encoder) throws { // 编码已知属性 var knownContainer = encoder.container(keyedBy: CodingKeys.self) try knownContainer.encode(name, forKey: .name) try knownContainer.encode(age, forKey: .age) // 编码所有未知键 var unknownContainer = encoder.container(keyedBy: DynamicCodingKey.self) for (key, value) in unknownKeys { let codingKey = DynamicCodingKey(stringValue: key)! try unknownContainer.encode(value, forKey: codingKey) } } }
4. 使用示例
// 包含未知键的原始JSON let rawJson = """ { "name": "Alice", "age": 30, "gender": "female", "hobbies": ["reading", "hiking"] } """.data(using: .utf8)! // 解码为模型 let decoder = JSONDecoder() let model = try decoder.decode(MyModel.self, from: rawJson) print(model.unknownKeys) // 输出: ["gender": .string("female"), "hobbies": .array([.string("reading"), .string("hiking")])] // 修改模型属性 var updatedModel = model updatedModel.age = 31 // 编码回JSON(保留未知键) let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let updatedJson = try encoder.encode(updatedModel) print(String(data: updatedJson, encoding: .utf8)!) // 输出的JSON会包含name、age(已修改)、gender、hobbies所有键
关键点说明
AnyCodable枚举覆盖了JSON支持的所有基础类型,确保未知值能被正确序列化/反序列化。DynamicCodingKey允许我们动态处理任意字符串键,无需提前定义所有可能的键。- 自定义
init(from:)和encode(to:)方法,分别完成未知键的捕获和回写,保证转换前后JSON的完整性。
内容的提问来源于stack exchange,提问作者fhucho
相关产品推荐
相关产品推荐

