How to compare two nested Dictionaries for equality?

How best do I compare to deeply nested Dictionaries for equality in a unit test? They are both of type [AnyHashable:Any]. My first pass of this:


    private func compareNestedDictionaries(_ expected: [AnyHashable:Any], _ actual: [AnyHashable:Any]) -> Void {
        if expected.count != actual.count {
            XCTFail("The two objects are not equal")
            return
        }
        
        for (key, value) in expected {
            var value1 = value
            var value2 = actual[key]
            let type1 = type(of: value1)
            let type2 = type(of: value2)
            value1 = value1 as? type1
            value2 = value2 as? type1

            if type1 == type2 {
                if let value1 = value1 as? [AnyHashable:Any], let value2 = value2 as? [AnyHashable:Any] {
                    compareNestedDictionaries(value1, value2)
                    continue
                } else {
                    XCTAssertEqual(value1, value2)
                }
            } else {
                XCTFail("The two objects are not equal")
                return
            }
        }
    }
1 Like

There are multiple issues with your code, but the main problem is, you can't establish equality of two arbitrary [AnyHashable:Any] dictionaries. You can write such a test for something like [AnyHashable:AnyEquatable]. You can write or use an existing AnyEquatable type eraser, or you may be able to go with [AnyHashable: AnyHashable] if your values are all going to be Hashable.

For correct type casting see this thread: How to capture a type then cast to it

1 Like

I don't think I've seen any standard library of any language that does this totally correctly. Usual culprit is floating point numbers, which (in my opinion) shouldn't even conform to Equatable. E.g. in Ruby:

#!/usr/bin/ruby

a = { key: 0.1 * 3 }
b = { key: 0.3 }

p(a == b) # => false

Similar issues are with dates (off by mere microseconds, for example), and strings (which might be accidentally compared without unicode normalization, although Swift does take care of that).

If possible, I would suggest you define an accurate domain-specific definition of equality for your usecase