Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

swift

swift3

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.

like image 877
Rahul Katariya Avatar asked Aug 26 '16 11:08

Rahul Katariya


4 Answers

With Swift 5.5 you can easily cast it to NSDictionary, as it always succeeds:

XCTAssertEqual(actual as NSDictionary, expected as NSDictionary)
like image 192
toupper Avatar answered Sep 21 '22 05:09

toupper


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
like image 28
Dravidian Avatar answered Oct 19 '22 03:10

Dravidian


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).

like image 4
dfrib Avatar answered Oct 19 '22 02:10

dfrib


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.

like image 3
OOPer Avatar answered Oct 19 '22 03:10

OOPer