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

Swift中如何递归比较两个Codable对象并获取差异?

递归比较Swift Codable实例生成差异字典

需求说明

现有两个Lecture结构体实例oldLecture和newLecture,结构体定义如下:

struct Lecture: Codable {
    let title: String
    let class_times: [ClassTime]
    let color: [String: String]
}

struct ClassTime: Codable {
    let start: Double
    let len: Double
}

需要递归对比两个实例的所有属性(包括嵌套结构体、数组、字典),生成一个仅包含newLecture中已修改属性的字典,用于发送PUT请求到服务器。

举个例子:

var oldLecture = Lecture(title: "lecture1", 
                         class_times: [ClassTime(start: 1, len: 2)], 
                         color: ["fg": "#fff", "bg": "#000"])
var newLecture = Lecture(title: "lecture1", 
                         class_times: [ClassTime(start: 2, len: 3)], 
                         color: ["fg": "#fff", "bg": "#000"])

// 预期结果:仅包含`class_times`键,对应值为`[["start": 2, "len": 3]]`

已尝试的方法

以下方法仅做了表层属性对比,没有递归处理嵌套类型,无法满足需求:

guard let oldDict = oldLecture.asDictionary() else { return nil }
guard let newDict = newLecture.asDictionary() else { return nil }
var dict: [String: Any] = [:]
for (key, newVal) in newDict {
    if let oldVal = oldDict[key], oldVal != newVal {
        dict[key] = newVal
    }
}

解决方案(无第三方库)

我们可以实现一个递归对比函数,同时给Encodable添加扩展,将实例转为字典用于对比:

1. Encodable转字典扩展

先实现一个通用的扩展,把任意Codable实例转为字典:

extension Encodable {
    func asDictionary() -> [String: Any]? {
        guard let data = try? JSONEncoder().encode(self) else { return nil }
        return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
    }
}

2. 递归对比差异函数

编写递归函数,处理字典、数组、基础类型的对比:

func generateDiff(oldDict: [String: Any]?, newDict: [String: Any]?) -> [String: Any]? {
    guard let old = oldDict, let new = newDict else { return new }
    
    var diffResult: [String: Any] = [:]
    
    for (key, newValue) in new {
        let oldValue = old[key]
        
        switch (oldValue, newValue) {
        // 处理嵌套字典
        case let (oldNested as [String: Any], newNested as [String: Any]):
            if let nestedDiff = generateDiff(oldDict: oldNested, newDict: newNested), !nestedDiff.isEmpty {
                diffResult[key] = nestedDiff
            }
        // 处理数组:若数组元素不同,直接保留新数组(如果需要更细粒度的数组元素对比,可以扩展此处逻辑)
        case let (oldArray as [Any], newArray as [Any]):
            if oldArray != newArray {
                diffResult[key] = newValue
            }
        // 处理基础类型(字符串、数字等)
        default:
            if oldValue != newValue {
                diffResult[key] = newValue
            }
        }
    }
    
    return diffResult.isEmpty ? nil : diffResult
}

3. 使用示例

let oldLecture = Lecture(title: "lecture1", 
                         class_times: [ClassTime(start: 1, len: 2)], 
                         color: ["fg": "#fff", "bg": "#000"])
let newLecture = Lecture(title: "lecture1", 
                         class_times: [ClassTime(start: 2, len: 3)], 
                         color: ["fg": "#fff", "bg": "#000"])

if let oldDict = oldLecture.asDictionary(), let newDict = newLecture.asDictionary() {
    if let diff = generateDiff(oldDict: oldDict, newDict: newDict) {
        print(diff)
        // 输出:["class_times": [["start": 2, "len": 3]]]
        // 可直接将此字典转为JSON数据发送PUT请求
    }
}

补充说明

  • 如果需要对数组元素做更细粒度的递归对比(比如数组中单个结构体元素修改时,只保留该元素的差异),可以扩展数组分支的逻辑,逐个对比数组元素并生成差异数组。
  • 该实现完全基于Swift标准库,无需依赖任何第三方框架。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 04:18:17