Is there any way to check if two [String: Any] are identical ?
let actual: [[String: Any]] = [
["id": 12345, "name": "Rahul Katariya"],
["id": 12346, "name": "Aar Kay"]
]
var expected: [[String: Any]]!
if actual == expected {
print("Equal")
}
Basically i want Dictionary to conform to Equatable protocol in Swift 3.
With Swift 5.5 you can easily cast it to NSDictionary
, as it always succeeds:
XCTAssertEqual(actual as NSDictionary, expected as NSDictionary)
For Xcode 7.3, swift 2.2
A dictionary is of type : [String:AnyObject]
or simply put 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
For Xcode 8.beta 6, Swift 3
Dictionary is defined as:
struct Dictionary<Key : Hashable, Value> : Collection, ExpressibleByDictionaryLiteral
NSDictionary has the following convenience initializer:
convenience init(dictionary otherDictionary: [AnyHashable : Any])
So you can use AnyHashable type for Key and Any type for Value
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
Conformance to Equatable
aside; for the exercise you could write your own isEqual
function to compare two [T: Any]
dictionaries for a subset of (Equatable
) types that you know the value wrapped by Any
is limited to. By attempted conversion to these types (e.g. in a switch
statement, as below), you could compare the dictionary's values (for each given key) one by one after their conversion to these given types. E.g.
// 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
}
}
Usage:
/* 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]' */
Just note the danger that the as
conversion may yield a false positive (true
) as it can allow mapping from two different types to a common other type, e.g. slicing away derived class differences when casting two derived class instances to their common parent type:
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"
If you'd rather like a failed "known types conversion" to return nil
(whereas successful conversions will always yield true
/false
, based on subsequent equality testing), you could extend the above to (the even messier)
// 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 */
Note however that the value by value equality testing above is short-circuited in case of a false
hit, which mean that depending on the random order of the non-ordered dictionaries (non-ordered collection), a special case may return nil
as well as false
, given two non-equal dictionaries. This special case occurs for two dictionary of non-equal values (non-equality for a known type value-value pair) which also hold an value type not included in the attempted casting: if the non-equality of known types is hit first, false
will be returned, whereas if a failed conversion is hit first, nil
will be returned. Either way, a nil
return means the call should be considered invalid, as caller stated that allPossibleValueTypesAreKnown
was true
(which a failed conversion implies is false
).
The type Any
is not Equatable
in Swift, so any collection types including Any
cannot be Equatable
.
You can write something like this in Swift 3/Xcode 8 beta 6:
if actual as NSArray == expected as NSArray {
print("Equal")
}
But, as importing id
as Any
is just introduced in beta 6, so this behaviour may change in the near future.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With