Matching Equatable aside; for the exercise, you can write your own isEqual function to compare two [T: Any] dictionaries for a subset of Equatable types, which are known to be limited to the value enclosed in Any . When you try to convert to these types (for example, in the switch , as shown below), you can compare the dictionary values โโ(for each given key) one after the other after converting them to these specified types. For instance.
// 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 } }
Using:
/* 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 pay attention to the danger that the as conversion can lead to a false positive value ( true ), since it can allow matching of two different types with a common other type, for example. discarding the differences of derived classes when throwing two instances of a derived class into 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: () }
If you want a failed conversion of known types to return nil (whereas successful conversions will always give true / false , based on subsequent equality testing), you can extend the above (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 } }
Please note, however, that the above testing for equality by value by value is short-circuited in the case of a false attack, which means that depending on the random order of unordered dictionaries (unordered set), a special case may return nil , as well as false , given two not equal dictionary. This special case occurs in two dictionaries of unequal values โโ(unevenness for a pair of values โโof a significant type), which also contain a value type that is not included in the casting attempt: if the unevenness of the known types first occurs, false will be returned, whereas if the conversion fails first, returns nil . In any case, returning nil means the call should be considered invalid because the caller stated that allPossibleValueTypesAreKnown was true (that a failed conversion means false ).