【问题标题】:Swift Loop through [String: Any] and get difference to Struct or Class propertiesSwift 循环遍历 [String: Any] 并获取 Struct 或 Class 属性的差异
【发布时间】:2019-10-18 22:18:59
【问题描述】:

我有一个具有多个属性的结构。如何编写一个函数,该函数采用 [String: Any] 类型的字典并创建另一个 [String: Any] 类型的字典,其中仅包含输入字典的值与具有相同名称的结构属性值不同的键和值?

一个结构:

struct MyStruct {
   var a: Bool = false
   var b: String = "random"
   var c: Int = 2
}

所需的函数调用:

let myStruct = MyStruct()
let input: [String: Any] = ["b": "test", "c": 2]
let result: [String: Any] = myStruct.getDiff(input)

示例输入的期望结果:

result = ["b": "test"]

除了结构之外,如何将 [String: Any] 与类进行比较?

【问题讨论】:

  • 你为什么要做这样的事情?在大多数情况下,当您尝试遍历一个类型的所有属性时,您做错了事,应该重新考虑您要解决的实际问题是什么以及为什么要选择这个特定的解决方案。
  • 我有一个代表存储为 Firebase Firestore 文档的模型的类。我想使用更改后的值向数据库发送一次更新。这需要发送字典。我刚刚意识到我已经在创建一个模型属性字典,我想比较两个字典会更好。
  • 最后一个解决方案的挑战是我需要比较 Any 类型的两个值类型
  • 不要比较字典,因为它们不是强类型的。至于将您的模型转换为字典,JSONSerialization 可以为您处理。或者,如果您只需要 JSON 字符串,JSONEncoder 并让您的课程 Encodable 也可以正常工作。

标签: swift


【解决方案1】:

您提供的特定语法可能是不可能的。将内容打包到[String: Any] 中可能会丢失太多无法恢复的信息。但你的总体目标肯定是可能的。

重要的是input。我们将使用显式类型 ValueChange,而不是 [String: Any]

let input = [
    ValueChange(key: "b", changedTo: "test"),
    ValueChange(key: "c", changedTo: 2),
]

像这样创建一个新类型允许我们捕获所有类型,并强制执行某些规则,特别是值是Equatable

struct ValueChange {
    init<Value: Equatable>(key: String, changedTo newValue: Value) {...}
}

我稍后会回到 ValueChange,但首先我想回到它的使用方式。由于您需要.getDiff(...) 语法,因此最好使用协议创建扩展:

protocol DictionaryDiffComputing {}

extension DictionaryDiffComputing {
    func getDiff(_ changes: [ValueChange]) -> [String: Any] {
        Dictionary(uniqueKeysWithValues:
            changes.compactMap { $0.changedValue(self) })
    }
}

协议没有要求。它只是说“这些类型具有getDiff 方法”。如果值已更改,此方法需要 ValueChange 为我们提供(String, Any) 元组。

这就是问题变得有趣的地方,我只是展示答案然后讨论它。

struct ValueChange {
    let changedValue: (_ object: Any) -> (String, Any)? // (key, newValue)

    init<Value: Equatable>(key: String, changedTo newValue: Value) {
        self.changedValue = { object in

            // Get the old value as an Any using Mirror
            guard let oldAnyValue: Any = Mirror(reflecting: object)
                .children
                .first(where: { $0.label == key })?
                .value
                else {
                    assertionFailure("Unknown key: \(key)")
                    return nil
            }

            // Make sure it's the correct type
            guard let oldValue = oldAnyValue as? Value else {
                assertionFailure("Bad type for values (\(oldAnyValue)). Expected: \(Value.self)")
                return nil
            }

            // Compare the values
            return newValue != oldValue ? (key, newValue) : nil
        }
    }
}

这使用 Mirror 提取旧值以作为 Any 类型进行比较,然后将其转换为正确的 Value 类型。这就是通用init 的强大之处。由于我们知道编译时的类型,我们可以在这个闭包中捕获它,从外部世界中删除该类型,但能够在运行时使用它。

extension MyStruct: DictionaryDiffComputing {}
let myStruct = MyStruct()
myStruct.getDiff(input) // ["b": "test"]

我真正不喜欢这个答案的是它非常不安全。请注意对assertionFailure 的两次调用。 ValueChange 没有任何东西可以确保键存在或值是正确的类型。如果您更改名称或键入属性,您的程序将崩溃或行为不正确,并且编译器无法帮助您。

您可以使这更安全,代码更简单,代价是调用语法稍微冗长:

protocol DictionaryDiffComputing {}

struct ValueChange<Root> {
    let changedValue: (_ object: Root) -> (String, Any)? // (key, newValue)

    init<Value: Equatable>(key: String, keyPath: KeyPath<Root, Value>, changedTo newValue: Value) {
        self.changedValue = { newValue != $0[keyPath: keyPath] ? (key, newValue) : nil }
    }
}

extension DictionaryDiffComputing {
    func getDiff(_ changes: [ValueChange<Self>]) -> [String: Any] {
        Dictionary(uniqueKeysWithValues:
            changes.compactMap { $0.changedValue(self) })
    }
}

let myStruct = MyStruct()

let input: [ValueChange<MyStruct>] = [
    ValueChange(key: "b", keyPath: \.b, changedTo: "test"),
    ValueChange(key: "c", keyPath: \.c, changedTo: 2),
]

myStruct.getDiff(input)

如果您使用这种方法,您就知道该类型上存在该属性,并且该值是该属性的正确类型。您还可以获得一些额外的功能,因为您可以使用从该根类型开始的任何您喜欢的密钥路径。这意味着您可以执行以下操作:

ValueChange(key: "b_length", keyPath: \.b.count, changedTo: 4),

您可以通过向键名添加一些键路径的映射字典(例如 static var 协议要求)来清除 ValueChange 中对 key 的要求,但我不知道如何生成它自动,我不知道有什么好方法可以将键路径转换为适当的字符串。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-11
    • 1970-01-01
    • 2014-12-19
    • 2016-05-26
    相关资源
    最近更新 更多