Flight-School / AnyCodable

Type-erased wrappers for Encodable, Decodable, and Codable values
https://flight.school/books/codable
MIT License
1.28k stars 132 forks source link

There is a bug in "Equatable" when the value is Array or Dictionary. #59

Open 623637646 opened 3 years ago

623637646 commented 3 years ago

print(AnyCodable.init(["1"]) == AnyCodable.init(["1"])) // print false print(AnyCodable.init(["1:2"]) == AnyCodable.init(["1:2"])) // print false

I am using AnyCodable-FlightSchool 0.6.0

ph4r05 commented 2 years ago

@623637646 I solved deep comparison of [String: AnyCodable]s with nested types in the following way:

func eqAny(_ lhs: Any?, _ rhs: Any?) -> Bool {
 if lhs == nil && rhs == nil { return true }
        if (lhs == nil) != (rhs == nil) { return false }

        var lhsOut = lhs
        var rhsOut = rhs
        while(true) {
            if let x = lhsOut as? AnyCodable {
                lhsOut = x.value
            } else { break }
        }

        while(true) {
            if let x = rhsOut as? AnyCodable {
                rhsOut = x.value
            } else { break }
        }

        switch (lhsOut, rhsOut) {
        case is (Void, Void):
            return true
        case let (lhs as AnyCodable, rhs as AnyCodable):
            return eqAny(lhs.value, rhs.value)
        case let (lhs as Bool, rhs as Bool):
            return lhs == rhs
        case let (lhs as Int, rhs as Int):
            return lhs == rhs
        case let (lhs as Int8, rhs as Int8):
            return lhs == rhs
        case let (lhs as Int16, rhs as Int16):
            return lhs == rhs
        case let (lhs as Int32, rhs as Int32):
            return lhs == rhs
        case let (lhs as Int64, rhs as Int64):
            return lhs == rhs
        case let (lhs as UInt, rhs as UInt):
            return lhs == rhs
        case let (lhs as UInt8, rhs as UInt8):
            return lhs == rhs
        case let (lhs as UInt16, rhs as UInt16):
            return lhs == rhs
        case let (lhs as UInt32, rhs as UInt32):
            return lhs == rhs
        case let (lhs as UInt64, rhs as UInt64):
            return lhs == rhs
        case let (lhs as Float, rhs as Float):
            return lhs == rhs
        case let (lhs as Double, rhs as Double):
            return lhs == rhs
        case let (lhs as String, rhs as String):
            return lhs == rhs
        case let (d1 as [Any?], d2 as [Any?]):
            return d1.count == d2.count
                    && d1.enumerated().map { off, el in eqAny(el, d2[off]) }.reduce(true) { $0 && $1 }
        case let (d1 as [String: Any?], d2 as [String: Any?]):
            return d1.count == d2.count && d1.keys == d2.keys
                    && d1.map { off, el in eqAny(el, d2[off]!) }.reduce(true) { $0 && $1 }
        default:
            return false
        }
    }

// compare with
eqAny(a, b)

Another, more naive option is to encode both to JSON and compare resulting data. For this to work, JSON has to be encoded in the same way. Problem can be with a dictionary key ordering. Coder would have to be adapted so it always encodes dictionary keys in a sorted order.