Swift:如何生成结构体/类属性与[String: Any]字典的差异字典?
没问题,我来帮你搞定这个需求!我们可以借助Swift的反射机制(Mirror)来实现这个功能,不管是结构体还是类,核心思路都是一致的——先获取类型的属性信息,再和输入字典逐一比对,筛选出值不同的键值对。
一、结构体的实现方案
我们可以给目标结构体扩展一个方法,利用反射遍历自身的所有属性,和输入字典做对比:
struct MyStruct { var a: Bool = false var b: String = "random" var c: Int = 2 } extension MyStruct { func getDiff(from input: [String: Any]) -> [String: Any] { var diffResult: [String: Any] = [:] // 通过Mirror获取结构体的属性反射信息 let mirror = Mirror(reflecting: self) for property in mirror.children { // 确保属性有名称,且输入字典包含该键 guard let propertyName = property.label, let inputValue = input[propertyName] else { continue } // 优先用可哈希类型做精准对比(覆盖大部分基础类型) if let selfValue = property.value as? AnyHashable, let inputHashable = inputValue as? AnyHashable { if selfValue != inputHashable { diffResult[propertyName] = inputValue } } else { // 针对数组、字典这类不可哈希的类型,用字符串描述做对比(如果需要更严谨可自定义相等逻辑) if "\(property.value)" != "\(inputValue)" { diffResult[propertyName] = inputValue } } } return diffResult } } // 测试代码 let myStruct = MyStruct() let input: [String: Any] = ["b": "test", "c": 2] let result = myStruct.getDiff(from: input) print(result) // 输出 ["b": "test"],完全符合预期
二、类的实现方案
类的实现和结构体几乎完全一致,因为Swift的Mirror同样支持类类型的反射。只需要把扩展的对象改成类即可:
class MyClass { var a: Bool = false var b: String = "random" var c: Int = 2 init() {} } extension MyClass { func getDiff(from input: [String: Any]) -> [String: Any] { var diffResult: [String: Any] = [:] let mirror = Mirror(reflecting: self) for property in mirror.children { guard let propertyName = property.label, let inputValue = input[propertyName] else { continue } if let selfValue = property.value as? AnyHashable, let inputHashable = inputValue as? AnyHashable { if selfValue != inputHashable { diffResult[propertyName] = inputValue } } else { if "\(property.value)" != "\(inputValue)" { diffResult[propertyName] = inputValue } } } return diffResult } } // 测试代码 let myClass = MyClass() let inputClass: [String: Any] = ["a": true, "c": 3, "d": "extra"] let resultClass = myClass.getDiff(from: inputClass) print(resultClass) // 输出 ["a": true, "c": 3],自动忽略了结构体/类中不存在的键"d"
额外注意事项
- 性能考量:反射操作会有一定性能开销,如果这个方法需要被频繁调用,建议手动维护一个属性名称列表(比如定义静态数组
static let properties = ["a", "b", "c"]),然后遍历这个列表做对比,避免反射的性能损耗。 - 类型安全:因为输入字典是
[String: Any],如果输入值的类型和结构体/类属性类型不匹配(比如把Int传给Bool属性),上面的代码会进入字符串对比逻辑。如果需要严格的类型检查,可以在对比前先判断类型是否一致。 - 嵌套类型支持:如果结构体/类包含嵌套的自定义类型,上面的代码只会对比顶层属性。如果需要支持嵌套属性的差异对比,需要修改代码增加递归遍历嵌套类型的逻辑。
内容的提问来源于stack exchange,提问作者Kinergy
相关产品推荐
相关产品推荐

