Repository: godot
Issue: Updating Dictionary keys in a Tool script cause it to display wrong keys in editor
Labels: bug,topic:gdscript,topic:editor

### Tested versions

Tested versions: 4.3 stable

### System information

Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4070 (nvidia, 556.12) - Ryzen 5 5600x

### Issue description

I'm making a dialogue system, which uses Nodes that have a `@tool` script attached to it.
Based on event type, I'm updating a keys in a Dictionary that's inside an Array, and when doing so, it seems like only size + 1 keys are being added, but keys prior to that are not changed in the editor (but changed in the process)

### Steps to reproduce
### Tested versions

Tested versions: 4.3 stable

### System information

Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4070 (nvidia, 556.12) - Ryzen 5 5600x

### Issue description

I'm making a dialogue system, which uses Nodes that have a @tool script attached to it.
Based on event type, I'm updating a keys in a Dictionary that's inside an Array, and when doing so, it seems like only size + 1 keys are being added, but keys prior to that are not changed in the editor (but changed in the process)

### Steps to reproduce

Create a tool script, which exports an Array variable, which has a Dictionary inside.
Make it so if a bool in the Dictionary changes, modify a key/value in the Dictionary.
Also print_debug when the Dictionary changes.
Notice that the Dictionary keys will mismatch from the print_debug version.
You will also be unable to change the value of the key that's faulty, until you reload the Node that the script is attached to.

```
@tool
extends Node

var dialogue: Array = [{
	"isMessage": true,
	"message": ""
}]

func _get_property_list() -> Array:
	var ret: Array = []

	ret.append({
		"name": "Messages",
		"type": TYPE_ARRAY,
		"hint": PROPERTY_HINT_ARRAY_TYPE,
		"hint_string": ""
	})

	return ret

func _set(prop_name: StringName, val) -> bool:
	match prop_name:
		"Messages":
			dialogue = val
			_update_fields()
			return true
		_:
			return false

func _get(prop_name: StringName):
	match prop_name:
		"Messages":
			return dialogue
		_:
			return null
			
func _update_fields():
	for index in range(dialogue.size()):
		var item = dialogue[index]
		
		if item == null:
			dialogue[index] = {
				"isMessage": true,
				"message": ""
			}
		else:
			if item.has("isMessage"):
				if item["isMessage"]:
					# When isMessage is true, clear message and ensure no event/params
					item["message"] = ""
					item.erase("event")
					item.erase("params")
					
				else:
					# When isMessage is false, ensure event and params are set
					if not item.has("event"):
						item["event"] = ""
					if not item.has("params"):
						item["params"] = [""]
						
					# Remove message if it exists
					item.erase("message")
	
	print_debug(dialogue)
```

### Minimal reproduction project (MRP)

https://github.com/SweetRazory/godot-tool-dictionary-bug


### Minimal reproduction project (MRP)

https://github.com/SweetRazory/godot-tool-dictionary-bug

[godot-tool-dictionary-bug.zip](https://github.com/user-attachments/files/16996944/godot-tool-dictionary-bug.zip)