Dictionary automatically sorting entries based on keys

Godot Version

4.4.stable.mono

Question

So, for a little context, I come from a Python background, so I’m not used to this, and it’s extremely annoying to me. Why is Dictionary behaviour so inconsistent? I can create a Dictionary in a script and it will preserve its order, but if I create the exact same Dictionary but on an @exported Dictionary variable, it wil get ordered based on the keys. This, to put it in mild terms, makes me come up with some very creative things to say to my PC.

A little calmer now – My main issue is this:

var dict: Dictionary[String, Variant] = ["First":1, "Apple":Vector2.ONE, "Last":"But not least"]
@export var exported_dict: Dictionary[String, Variant]
#this Dictionary has the exact same values and order as dict,
#just added in the editor

func _ready() -> void:
    print(dict.keys(), exported_dict.keys())
    #prints ["First", "Apple", "Last"]["Apple", "First", "Last"]

This is extremely inconsistent, and again, coming from a background where data has to stay the way the developer/user wants it to be properly processed, this is incredibly annoying. And it doesn’t stop there, because it’s not that the keys() function orders the Dictionary’s keys; the Dictionary itself gets ordered. Even calling a breakpoint on the print line shows that on runtime, exported_dict is ordered based on its keys, while dict isn’t. This doesn’t even allow me to manually iterate over the Dictionary’s keys to ensure it preserves the intended order, and I’m basically forced to add something like integers into my string keys to ensure that the order is preserve, so instead of "First", "Apple", "Last" I’d have to write "0First", "1Apple", "2Last" to preserve the order I want.

Now, for the reason why I can’t just ignore this:
In any other situation, I’d just live with it. But what I’m doing right now is this: I’m making a “mesh assembler” that takes multiple meshes, their names (as individual strings), and spatial information, and adds them to the scene tree in _ready. the meshes and the names are stored in a Dictionary, with the strings being the keys (duh). The spatial information, i.e. position, rotation and scale, are stored in three separate PackedVector3Arrays. All of these variables are export variables. With the way the export Dictionary is behaving, I have to edit the spatial information based on the runtime order, not the order of insertion, making things extremely difficult.

I hope this explains the nature of my issue well enough, and I genuinely hope I’m not the only one who’s… extremely annoyed… by this. For now, I’m stuck to using the editor, but sometime in the future, I’ll be using the game itself to make these edits. But it would burn so much time to add all the different meshes, run the game, see the runtime order, and then edit the spatial information based on the runtime order.

Exported dictionaries sorting their keys is a known issue Exported dictionaries have their keys sorted alphabetically · Issue #90142 · godotengine/godot · GitHub There’s a PR opened here Remove dictionary sort in VariantWriter by dog-on-moon · Pull Request #104164 · godotengine/godot · GitHub

1 Like

It’s not ideal, but there are workarounds while you wait for a real fix. A few questions:

  • Does order matter other than linking components? That is, if the arrays were kept in sync somehow, would it matter if the order changed?
  • Are your PackedVector3Arrays per-entry, or do all your scale entries share one scale array?

Personally, I’d be tempted to do something like:

var Data: Array = [
    { "name": "First", "mesh": first_mesh, "pos": first_pos, ... },
    { "name": "Apple", "mesh": apple_mesh, "pos": apple_pos, ... },
    ...
]

If you want your positional info packed in shared PackedVector3Arrays you could either rely on the array ordering being stable, or you could store offset/size info in each dict.

Accessing things becomes slightly more complex, since rather than Data["Apple"] you’d probably want to roll getters:

func get_scale_from_name(nom: String, set: Array) -> Vector3:
    var el: Dictionary = get_data_from_name(nom, set)
    if el.has("scale") return el["scale"]
    return Vector3(1.0, 1.0, 1.0)

func get_data_from_name(nom: String, set: Array) -> Dictionary:
    for el in set:
        if nom == el["name"]: return el
    print("No element called %s?" % nom)
    return {}
1 Like

Thanks! I’m glad it’s not just me lol

I can live with these workarounds, it’s just that I want to keep things optimized in terms of memory because the assembler could be dealing with a large number of meshes (which in itself is already horrible for optimization). Thanks for the help btw, appreciate it:)

If you really want to make things memory optimal, there is PackedByteArray, inside which you could roll whatever data structure you like…

1 Like