JSON parsed dictionary hash differs from hardcoded dictionary hash

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By CodeCraft

I load a dictionary from a json file, but when comparing hashes the same dictionary hardcoded in the script has a different hash and also sometimes behaves different.
My file (test.level):

{"meta":{"stars":[5,6,7]},"0":{"x":-300,"y":-600,"type":"BUTTON-ON"},"1":{"x":-180,"y":-600,"type":"BUTTON-ON"},"2":{"x":-60,"y":-600,"type":"BUTTON-OFF"},"3":{"x":60,"y":-600,"type":"BUTTON-ON"},"4":{"x":180,"y":-600,"type":"BUTTON-ON"},"5":{"x":300,"y":-600,"type":"BUTTON-ON"},"6":{"x":-240,"y":-450,"type":"XOR","source1":0,"source2":1},"7":{"x":0,"y":-450,"type":"AND","source1":2,"source2":3},"8":{"x":240,"y":-450,"type":"XOR","source1":4,"source2":5},"9":{"x":0,"y":-300,"type":"SWITCH-OFF","source":7},"10":{"x":-120,"y":-150,"type":"OR","source1":6,"source2":-9},"11":{"x":120,"y":-150,"type":"AND","source1":9,"source2":8},"12":{"x":0,"y":0,"type":"AND","source1":10,"source2":11},"13":{"x":0,"y":150,"type":"GOAL","sources":[12]}}

My code:

var level_data = JSON.parse(read_file("user://test.level")).result
var level_data2 = {"meta":{"stars": [5, 6, 7]},"0":{"x":-300,"y":-600,"type":"BUTTON-ON"},"1":{"x":-180,"y":-600,"type":"BUTTON-ON"},"2":{"x":-60,"y":-600,"type":"BUTTON-OFF"},"3":{"x":60,"y":-600,"type":"BUTTON-ON"},"4":{"x":180,"y":-600,"type":"BUTTON-ON"},"5":{"x":300,"y":-600,"type":"BUTTON-ON"},"6":{"x":-240,"y":-450,"type":"XOR","source1":0,"source2":1},"7":{"x":0,"y":-450,"type":"AND","source1":2,"source2":3},"8":{"x":240,"y":-450,"type":"XOR","source1":4,"source2":5},"9":{"x":0,"y":-300,"type":"SWITCH-OFF","source":7},"10":{"x":-120,"y":-150,"type":"OR","source1":6,"source2":-9},"11":{"x":120,"y":-150,"type":"AND","source1":9,"source2":8},"12":{"x":0,"y":0,"type":"AND","source1":10,"source2":11},"13":{"x":0,"y":150,"type":"GOAL","sources":[12]}}
var level_data3 = JSON.parse("{\"meta\":{\"stars\":[5,6,7]},\"0\":{\"x\":-300,\"y\":-600,\"type\":\"BUTTON-ON\"},\"1\":{\"x\":-180,\"y\":-600,\"type\":\"BUTTON-ON\"},\"2\":{\"x\":-60,\"y\":-600,\"type\":\"BUTTON-OFF\"},\"3\":{\"x\":60,\"y\":-600,\"type\":\"BUTTON-ON\"},\"4\":{\"x\":180,\"y\":-600,\"type\":\"BUTTON-ON\"},\"5\":{\"x\":300,\"y\":-600,\"type\":\"BUTTON-ON\"},\"6\":{\"x\":-240,\"y\":-450,\"type\":\"XOR\",\"source1\":0,\"source2\":1},\"7\":{\"x\":0,\"y\":-450,\"type\":\"AND\",\"source1\":2,\"source2\":3},\"8\":{\"x\":240,\"y\":-450,\"type\":\"XOR\",\"source1\":4,\"source2\":5},\"9\":{\"x\":0,\"y\":-300,\"type\":\"SWITCH-OFF\",\"source\":7},\"10\":{\"x\":-120,\"y\":-150,\"type\":\"OR\",\"source1\":6,\"source2\":-9},\"11\":{\"x\":120,\"y\":-150,\"type\":\"AND\",\"source1\":9,\"source2\":8},\"12\":{\"x\":0,\"y\":0,\"type\":\"AND\",\"source1\":10,\"source2\":11},\"13\":{\"x\":0,\"y\":150,\"type\":\"GOAL\",\"sources\":[12]}}").result
print(level_data.hash())
print(level_data2.hash())
print(level_data3.hash())

The output I get is:

137978820
2281966234
137978820

So the first and third are the same (the one from the JSON-File and the one from the JSON-String), but the second (hardcoded) is different and the first and third respond with an error when trying to read keys from the dictionary.
How could I:

  1. Compare those two correctly?
  2. Read the keys from the ones read from JSON?

Thanks in advance

:bust_in_silhouette: Reply From: Lopy

The difference in hashes comes from the loss of information described in the documentation for JSONParseResult.result. Godot saves the ordering of Dictionary keys, but JSON does not (see also the number difference).

1

Here is a deep comparison function to compare your Dictionaries:

func deep_equal(a, b) -> bool:
    var type := typeof(a)
    var type_b := typeof(b)
    
    if type != type_b: # Check types
        # We might have float and int
        
        if type == TYPE_INT && type_b == TYPE_REAL:
            return is_equal_approx(a, b)
        elif type == TYPE_REAL && type_b == TYPE_INT:
            return is_equal_approx(a, b)
        
        else: # Truly different types
            return false
    
    match type:
        TYPE_DICTIONARY:
            if a.size() != b.size():
                return false # Different count of keys
            
            for key in a.keys():
                if !b.has(key):
                    return false # Missing key
                
                if !deep_equal(a[key], b[key]):
                    return false # Wrong value
        
        TYPE_ARRAY:
            if a.size() != b.size():
                return false # Wrong size
            
            for i in a.size():
                if !deep_equal(a[i], b[i]):
                    return false # Wrong value
        
        _: # Catchall, more cases might be necessary
            return a == b # Regular equal
    
    return true # To silence errors

2

I could not reproduce your key reading error, here is my testing function:

func _run() -> void:
    var level_data2 = {"meta":{"stars": [5, 6, 7]},"0":{"x":-300,"y":-600,"type":"BUTTON-ON"},"1":{"x":-180,"y":-600,"type":"BUTTON-ON"},"2":{"x":-60,"y":-600,"type":"BUTTON-OFF"},"3":{"x":60,"y":-600,"type":"BUTTON-ON"},"4":{"x":180,"y":-600,"type":"BUTTON-ON"},"5":{"x":300,"y":-600,"type":"BUTTON-ON"},"6":{"x":-240,"y":-450,"type":"XOR","source1":0,"source2":1},"7":{"x":0,"y":-450,"type":"AND","source1":2,"source2":3},"8":{"x":240,"y":-450,"type":"XOR","source1":4,"source2":5},"9":{"x":0,"y":-300,"type":"SWITCH-OFF","source":7},"10":{"x":-120,"y":-150,"type":"OR","source1":6,"source2":-9},"11":{"x":120,"y":-150,"type":"AND","source1":9,"source2":8},"12":{"x":0,"y":0,"type":"AND","source1":10,"source2":11},"13":{"x":0,"y":150,"type":"GOAL","sources":[12]}}
    
    var level_data3 = JSON.parse('{"meta":{"stars":[5,6,7]},"0":{"x":-300,"y":-600,"type":"BUTTON-ON"},"1":{"x":-180,"y":-600,"type":"BUTTON-ON"},"2":{"x":-60,"y":-600,"type":"BUTTON-OFF"},"3":{"x":60,"y":-600,"type":"BUTTON-ON"},"4":{"x":180,"y":-600,"type":"BUTTON-ON"},"5":{"x":300,"y":-600,"type":"BUTTON-ON"},"6":{"x":-240,"y":-450,"type":"XOR","source1":0,"source2":1},"7":{"x":0,"y":-450,"type":"AND","source1":2,"source2":3},"8":{"x":240,"y":-450,"type":"XOR","source1":4,"source2":5},"9":{"x":0,"y":-300,"type":"SWITCH-OFF","source":7},"10":{"x":-120,"y":-150,"type":"OR","source1":6,"source2":-9},"11":{"x":120,"y":-150,"type":"AND","source1":9,"source2":8},"12":{"x":0,"y":0,"type":"AND","source1":10,"source2":11},"13":{"x":0,"y":150,"type":"GOAL","sources":[12]}}').result
    
    print(deep_equal(level_data2, level_data3))
    print(level_data3.meta)
    

PS

You can use ’ to delimit Strings that contain ". 'A String where " is a normal character, even """ is alright, no \" required'.

:bust_in_silhouette: Reply From: CodeCraft

I found the error myself: It was due to the general JSON definition: JSON doesn’t know floats or ints, it handles everything just as generic numeric type. Godot therefore parses that numeric type from JSON as float, whether there are decimals or not.