Dictionary equality question/problem

I was in a situation where I needed to compare two Dictionary objects.

More precisely: I expected value comparison (as opposed to "reference comparison").

It looks like the value comparison for dictionaries fails when the dictionaries' key order is different (even though they have the same keys and values).

Let's look at the following test:

```

import Toybox.Lang;
import Toybox.System;
import Toybox.Test;

(:test)
function testTheTester(logger as Logger) as Void {
    var dict1 = { "level1" => { "level2" => { "key1" => 1, "key2" => 2 } } };
    var dict2 = { "level1" => { "level2" => { "key2" => 2, "key1" => 1 } } };
    System.println("dict1: " + dict1);
    System.println("dict2: " + dict2);
    System.println("dict1.equals(dict2): " + dict1.equals(dict2));
    System.println("dict1.toString().equals(dict2.toString()): " + dict1.toString().equals(dict2.toString()));
    Test.assertEqual(dict1, dict2);
}
```
This test gives the following output:
------------------------------------------------------------------------------
Executing test testTheTester...
dict1: {level1=>{level2=>{key1=>1, key2=>2}}}
dict2: {level1=>{level2=>{key1=>1, key2=>2}}}
dict1.equals(dict2): false
dict1.toString().equals(dict2.toString()): true

Error: Unhandled Exception
Exception: ASSERTION FAILED
Stack:
  - assertMessage() at e6eec51.mb:186 0x300016da
  - assertEqual() at e6eec51.mb:215 0x30001590
  - testTheTester() at C:\Users\kbrejna\git-projects\garmin-projects\DictComparison\source\DictTest.mc:13 0x10000267
  - evaluate_test_entries_0_to_1() at C:\Users\kbrejna\git-projects\garmin-projects\DictComparison\bin\gen\006-B3906-00\source\UnitTests.mcgen:51 0x10000334
  - runTest() at C:\Users\kbrejna\git-projects\garmin-projects\DictComparison\bin\gen\006-B3906-00\source\UnitTests.mcgen:66 0x10000419
ERROR

==============================================================================
RESULTS
Test:                                Status:
testTheTester                        ERROR
Ran 1 test

FAILED (passed=0, failed=0, errors=1)
Any reasoning behind this? Or is it a bug?
(Reproduced on SDK 6.2.4, 4.0.9)
Regards,
Karol
P.S. Comparision on .toStrings() for both dictionaries works well...
  • I wouldn't use toString for checking things. It's nice that it seems that it prints the keys sorted, but it might change between devices, between compiler versions, who knows.

    Regarding Object.equals(): the documentation isn't very clear and even the given example is not very useful. Maybe they meant to say it's checking references? Not sure.

    If you really need this to work and robust then you'll need to write your own comparsion (you'll know if it needs to be recursive, how deep it can be, etc)

  • toString() method doesn't work all the time. I was mentioning it as a curiosity. 

    I believe it would be good to rely on the SDK/language for basic (native) operations as object comparison.

    Writing code for such trivial tasks shouldn't be required.

    I may be biased, hence the question here...

  • Do you need to know the differences, or just if they are different? There is a hashCode function that you could use to do a simple equality test. If you need to know the differences, then like it's been said you'll need to do that yourself.

  • Again, let's take a look a the docs (https://developer.garmin.com/connect-iq/api-docs/Toybox/Lang/Object.html#hashCode-instance_function):

    hashCode() as Lang.Number

    Get a hash code value for an Object.

    This computes a 32-bit Number that is typically used as an index when placing Objects into a Dictionary. Hash code values have the following characteristics:

    • The computed hash code is constant for the lifetime of an Object

    • If two Objects are equal, their hash codes will be equal

    If the documentation is right (and we understand "objects are equal" as value equality), hashCode() trick could work.

    Let's check:

        var number1 = 3;
        var number2 = 3;
        System.println("number1.hashCode(): " + number1.hashCode());
        System.println("number1.hashCode(): " + number2.hashCode());
        System.println("number1 == number2:" + (number1 == number2));
        System.println("number1.equals(number2): " + number1.equals(number2));
    
        var dict1 = { "key1" => 1 };
        var dict2 = { "key1" => 1 };
        System.println("dict1.hashCode(): " + dict1.hashCode());
        System.println("dict2.hashCode(): " + dict2.hashCode());
        System.println("dict1 == dict2: " + (dict1 == dict2));
        System.println("dict1.equals(dict2): " + dict1.equals(dict2));
        System.println("dict2.equals(dict1): " + dict2.equals(dict1));

    What we get is:

    number1.hashCode(): 3
    number1.hashCode(): 3
    number1 == number2:true
    number1.equals(number2): true
    
    dict1.hashCode(): 132
    dict2.hashCode(): 143
    dict1 == dict2: false
    dict1.equals(dict2): false
    dict2.equals(dict1): false
    

    For numbers, it seems right. number1 and number2 are equal (both value and reference) and they have the same hash.

    Dictionaries behave differently. Dictionary with value { "key1" => 1 } is not equal to another dictionary { "key1" => 1 }, they hash codes are different.

    As you can see, .equals() is inconsistent. Probably that's why hashCode() is "strange", too, for Dictionary objects.

  • It's not inconsistent. It's just different from what we think it should be. It might be a bug or a bug in the documentation. Seemingly a.equals(b) is the same as a == b. So you'll have to implement your own comparator.