如何检查两个 [String: Any] 是否相同?

How to check if two [String: Any] are identical?

有什么方法可以检查两个 [String: Any] 是否相同?

let actual: [[String: Any]] = [
    ["id": 12345, "name": "Rahul Katariya"],
    ["id": 12346, "name": "Aar Kay"]
]
var expected: [[String: Any]]!

if actual == expected {
    print("Equal")
}

基本上我希望 Dictionary 符合 Swift 3.

中的 Equatable 协议

对于Xcode7.3,swift2.2 字典的类型是:[String:AnyObject] 或简单地说 NSDictionary

let actual: [String: AnyObject] = ["id": 12345, "name": "Rahul Katariya"]


var expected: [String: AnyObject] = ["id": 12346, "name": "Aar Kay"]


print(NSDictionary(dictionary: actual).isEqualToDictionary(expected))//False

对于Xcode8.beta6,Swift3

字典定义为:

struct Dictionary<Key : Hashable, Value> : Collection, ExpressibleByDictionaryLiteral

NSDictionary 具有以下便捷初始化程序:

convenience init(dictionary otherDictionary: [AnyHashable : Any])

因此您可以对 Key 使用 AnyHashable 类型,对 Value 使用 Any 类型

let actual: [String: Any] = ["id": 12345, "name": "Rahul Katariya"]

var expected: [String: Any] = ["id": 12346, "name": "Aar Kay"]


print(NSDictionary(dictionary: actual).isEqual(to: expected))//False

类型 Any 不是 Swift 中的 Equatable,因此包括 Any 在内的任何集合类型都不能是 Equatable

你可以在 Swift 3/Xcode 8 beta 6:

中写这样的东西
if actual as NSArray == expected as NSArray {
    print("Equal")
}

但是,由于 importing id as Any 刚刚在 beta 6 中引入,因此这种行为可能会在不久的将来改变。

符合 Equatable 放在一边;在这个练习中,您 可以 编写自己的 isEqual 函数来比较两个 [T: Any] 词典中您知道值的 (Equatable) 类型的子集被 Any 包裹限制为。通过尝试转换为这些类型(例如,在 switch 语句中,如下所示),您可以 它们转换为这些给定的类型。例如

// Usable if the 'Any' values in your dict only wraps
// a few different types _that are known to you_.
// Return false also in case value cannot be successfully 
// converted to some known type. This might yield a false negative.
extension Dictionary where Value: Any {
    func isEqual(to otherDict: [Key: Any], 
                 allPossibleValueTypesAreKnown: Bool = false) -> Bool {
        guard allPossibleValueTypesAreKnown && 
            self.count == otherDict.count else { return false }
        for (k1,v1) in self {
            guard let v2 = otherDict[k1] else { return false }
            switch (v1, v2) {
                case (let v1 as Double, let v2 as Double) : if !(v1.isEqual(to: v2)) { return false }
                case (let v1 as Int, let v2 as Int) : if !(v1==v2) { return false } 
                case (let v1 as String, let v2 as String): if !(v1==v2) { return false }
                // ... fill in with types that are known to you to be 
                // wrapped by the 'Any' in the dictionaries
                default: return false
            }
        }
    return true
    } 
}

用法:

/* example setup */
var dict1: [String: Any] = ["id": 12345, "name": "Rahul Katariya", "weight": 70.7]
var dict2: [String: Any] = ["id": 12346, "name": "Aar Kay", "weight": 83.1]

/* example usage */
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
    // false

dict2["name"] = "Rahul Katariya"
dict2["weight"] = 70.7

print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true)) 
    // false

dict2["id"] = 12345

print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true)) 
    // true

class Foo {}
dict1["id"] = Foo()
dict2["id"] = Foo()

print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))  
    // false! (we haven't implemented this attempted conversion!)

// incompatable keys cause error as expected an intended    
let dict3: [Int: Any] = [1:2]
dict1.isEqual(to: dict3)
    /* error: cannot convert value of type '[Int : Any]' 
              to expected argument type '[String : Any]' */

请注意 as 转换可能会产生误报 (true) 的危险,因为它允许从两种不同类型映射到其他常见类型,例如将两个派生的 class 实例转换为它们共同的父类型时,切掉派生的 class 差异:

class Base: Equatable {}
func ==(lhs: Base, rhs: Base) -> Bool { return true }

class DerivedA : Base {
    let foo = "foo"
}

class DerivedB : Base {
    let bar = 4.2
}

let a = DerivedA()
let b = DerivedB()

switch (a, b) {
    case (let a as Base, let b as Base): print(a == b) 
    default: ()
} // sliced by conversion! prints "true"

如果您希望将失败的 "known types conversion" 转换为 return nil(而成功的转换将始终产生 true/false,基于随后的平等测试),你可以将上面的内容扩展到(甚至更混乱)

// a 'nil' return here would correspond to an invalid call
extension Dictionary where Value: Any {
    func isEqual(to otherDict: [Key: Any], 
                 allPossibleValueTypesAreKnown: Bool = false) -> Bool? {
        guard allPossibleValueTypesAreKnown else { return nil } 
        guard self.count == otherDict.count else { return false }
        for (k1,v1) in self {
            guard let v2 = otherDict[k1] else { return false }
            switch (v1, v2) {
                case (let v1 as Double, let v2 as Double) : if !(v1.isEqual(to: v2)) { return false }
                case (let v1 as Int, let v2 as Int) : if !(v1==v2) { return false } 
                case (let v1 as String, let v2 as String): if !(v1==v2) { return false }
                // ... 
                case (_ as Double, let v2): if !(v2 is Double) { return false }
                case (_, _ as Double): return false
                case (_ as Int, let v2): if !(v2 is Int) { return false }
                case (_, _ as Int): return false
                case (_ as String, let v2): if !(v2 is String) { return false }
                case (_, _ as String): return false 
                default: return nil
            }
        }
    return true
    } 
}

/* Example as per above will yield (printout):

       Optional(false)
       Optional(false)
       Optional(true)
       nil                           */

但是请注意,在 false 命中的情况下,上述按值相等性测试的值是短路的,这意味着取决于无序字典(无序集合)的随机顺序,给定两个不相等的字典,特殊情况可能 return nil 以及 false。这种特殊情况发生在两个不相等值的字典(已知类型值-值对的不相等)中,它们还包含未包含在尝试的转换中的值类型:如果首先命中已知类型的不相等, false 将被 returned,而如果首先命中失败的转换,nil 将被 returned。无论哪种方式,nil return 意味着调用应该被视为无效,因为调用者声明 allPossibleValueTypesAreKnowntrue(转换失败意味着是 false) .

使用 Swift 5.5,您可以轻松地将其转换为 NSDictionary,因为它总是成功:

XCTAssertEqual(actual as NSDictionary, expected as NSDictionary)