Marcel
Marcel

Reputation: 502

compare two properties of type Any

I'm trying to compare the properties of two instances of a class. I used this before and it's working fine:

override public func isEqual(_ object: Any?) -> Bool {
    if let rhs = object as? BankDataModel, self.accountHolderName == rhs.accountHolderName, self.accountNumber == rhs.accountNumber,
        self.accountHolderThirdPartyAccountOwner == rhs.accountHolderThirdPartyAccountOwner, self.bankName == rhs.bankName, self.bankRoutingNumber == rhs.bankRoutingNumber, self.bic == rhs.bic, self.iban == rhs.iban, self.directDebitStatus == rhs.directDebitStatus, self.sepaCreditorID == rhs.sepaCreditorID, self.sepaMandateID == rhs.sepaMandateID{
        return true
    }else{
        return false
    }
}

However I have to write this for all of my model classes again if I want to use this. I want to have a function which I can use for all my model classes, so I have used the Mirror struct and tried something like this:

 override public func isEqual(_ object: Any?) -> Bool {
    if let rhs = object as? BankDataModel{
        return compareTwoObjects(objectToCompare: rhs, objectSelf: self)
    }else{
        return false
    }
}

The overridden isEqual method in each model will call this method from a Helper class, so I can use this in every Model that I have:

func compareTwoObjects(objectToCompare: Any, objectSelf: Any) -> Bool{
    let objectSelf2 = Mirror(reflecting: objectSelf)
    let objectToCompare2 = Mirror(reflecting: objectToCompare)
    var index = objectToCompare2.children.startIndex
    for attr in objectSelf2.children{
        let type1 = Mirror(reflecting: attr.value).subjectType
        print(type1)
        let type2 = Mirror(reflecting: objectToCompare2.children[index].value).subjectType
        print(type2)
        if type1 == type2{
            print("Equal")
            if attr.value as! type1 != objectToCompare2.children[index].value as! type2{
                return false
            } 
        }
        index = objectToCompare2.children.index(index, offsetBy: 1)
    }
    return true
}

The most of the code is working fine, the print("Equal") is always executed when the types of the two variables are equal. The problem is this line:

if attr.value as! type1 != objectToCompare.children[index].value as! type2{
      return false
 }

I get this error message: "Use of undeclared type "type1"". If I don't use the casting to type1 and type2, I get the error that I cannot use == with two properties of type "Any". Is there a way to convert the type1 and type2 Strings to real datatypes? Or is there maybe a better way for what I'm trying to achieve?

This is my output from the prints:

Optional<String> Optional<String> Equal Optional<String> Optional<String> Equal Optional<Bool> Optional<Bool> Equal String String Equal Optional<String> Optional<String> Equal String String Equal String String Equal Optional<DirectDebitStatus> Optional<DirectDebitStatus> Equal

I would appreciate any help!

Upvotes: 2

Views: 793

Answers (3)

Johan
Johan

Reputation: 21

I had a similar problem. Needed to compare several (huge) model objects that I got from different sources. Starting with your code and expanding with recursion and special cases I ended up with the following code snippet.

(Please note that I use this in a Test class, so there's no need to be efficient.)

func compareProperties(of target1: Any, with target2: Any) -> Bool {
    
    let mirror1 = Mirror(reflecting: target1)
    let mirror2 = Mirror(reflecting: target2)
    var index = mirror1.children.startIndex
    
    for child in mirror1.children {
        let child2 = mirror2.children[index]
        
        //Make another mirror to see if it has children
        let innerMirror1 = Mirror(reflecting: child.value)
        
        if (innerMirror1.children.count > 0) {
            // Use recursive reflection on the value of each child.
            print("Recursion on \(innerMirror1.subjectType)")
            if (!compareProperties(of: child.value, with: child2.value)) {
                return false
            }
        } else {
            //Compare the values
            let type1 = innerMirror1.subjectType
            let innerMirror2 = Mirror(reflecting: child2.value)
            let type2 = innerMirror2.subjectType
            if type1 == type2 {
                
                let stringValue1 = "\(child.value)"
                let stringValue2 = "\(child2.value)"
                
                //print("\(type1): \(stringValue1) ==? \(type2): \(stringValue2)")
                                                
                if (stringValue1 != stringValue2) {
                    print("\(type1): \(stringValue1) != \(type2): \(stringValue2)")
                    //Special case with Dates
                    if ("\(mirror1.subjectType)" == "Date") {
                        //Skip comparison of Doubles in Dates
                        if ("\(target1 as? Date)" != "\(target2 as? Date)") {
                            return false
                        }
                    } else {
                        return false
                    }
                }
                
            } else {
                print("Different classes \(type1) and \(type2)")
                return false
            }
        }
        //Increase index for next value pair
        index = mirror1.children.index(index, offsetBy: 1)
    }
    //If we get here it was never false.
    return true
}

Upvotes: 2

Alex
Alex

Reputation: 3971

This might be what you're looking for, it compares two Any? objects by casting them to NSObject first.

    private func equalAny<BaseType: Equatable>(lhs: Any?, rhs: Any?, baseType: BaseType.Type) -> Bool {
    if (lhs == nil && rhs != nil) || (rhs == nil && lhs != nil) { return false }
    if lhs == nil && rhs == nil { return true }
    guard let lhsEquatable = lhs as? BaseType, let rhsEquatable = rhs as? BaseType else {
        return false
    }
    return lhsEquatable == rhsEquatable
}

Make sure to use NSObject as the base type. Call the method like this:

equalAny(lhs: value, rhs: otherValue, baseType: NSObject.self)

Upvotes: 1

Alex
Alex

Reputation: 907

Swift 4

I think you might want to use the Equatable interface. With it you can achieve something like this:

extension BankDataModel: Equatable {
    static func == (lhs: BankDataModel, rhs: BankDataModel) -> Bool {
        return
            lhs.accountHolderName == rhs.accountHolderName &&
            lhs.accountNumber == rhs.accountNumber &&
            lhs.accountHolderThirdPartyAccountOwner == rhs.accountHolderThirdPartyAccountOwner
    }
}

Note: you can extend the return statement with additional && clauses to increase the 'equal'-checking.

Then wherever you put this extension you can compare two BankDataModel with each other using the == operator, like so:

let bankAModel: BankDataModel = // Some model
let bankBModel: BankDataModel = // Also some model

if bankAModel == bankBModel {
  // Do stuff
}

Expansion

The best case I can think of is that you implement both Equatable and Hashable on BankDataModel so that it results into a clean comparison method on the Equatable side (only comparing hashes there).

This way results in better maintainable and cleaner code. See also separation of concerns pattern.

Upvotes: 4

Related Questions