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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 15:25:10