Topic was automatically imported from the old Question2Answer platform.
Asked By
thomash
Hello,
I’m trying to set data in a dictionary at an arbitrary path. I use this pattern a lot in other languages, but it seems that GDscript is handling data in dictionaries as copy and not as reference. Here is my code:
func set_data(path: NodePath, data) -> bool:
var segment = _my_data
for i in path.get_name_count():
var name = path.get_name(i)
if not segment.keys().has(name):
return false
segment = segment.get(name)
segment = data
prints(data, segment, _my_data.some.known.path)
return true
I call this viaset_data(NodePath("some/known/path"), "foo_bar")
Normally I would expect, that segment always holds a reference to the element in the dictionary following the path. But it doesn’t. The prints statement something like this.
"foo_bar" "foo_bar" "some_old_data"
I guess I need to take a different approach here. Can somebody help me, how to achieve this in GDscript?
Built-in types are passed by value, but types deriving from Object are passed by reference. If your data is a string or something, you’ll need to wrap it in an object.
Ex:
extends Object
class_name MyDictData
var data
Then, in your script: segment.data = data
Edit: to clarify, I mean that the “leaves” in your dictionary structure should be wrapped with the MyDictData type when you fill the dictionary. Although you don’t need static typing, you could also do _my_data.some.known.path = { "data": "some_old_data" }
Thanks. This would certainly help with this particular problem, but I guess it would make working with the data a bit more tedious everywhere else. Because I read the structure from a JSON file. I always have to wrap it somehow to account for the new Type.
Anyways, I solved it more or less like you suggested but I do it at another place. I am not wrapping the data in the beginning, but when I’m writing it.
I don’t fetch the data to last element in the path, but to the second to last. This makes pretty much sure, that I will get a reference to a dictionary.
I set the data to the key in that referenced dict and everything works as expected.
Looks like this now:
func set_level_data(path: NodePath, data) -> bool:
var segment = _current_level_data
for i in path.get_name_count() -1:
var name = path.get_name(i)
if not segment.keys().has(name):
return false
segment = segment[name]
segment[path.get_name(path.get_name_count() -1)] = data
return true
You seem to changing the type of segment. It starts as a dictionary, a copy of _my_data and then is changed to a string with segment = segment.get(name). Also, what is _my_data.some.known.path?
Is this meant to be a function "cache_path_segments_of_path)?
That is correct. I will try to explain a bit more, what I am doing here.
Maybe there is a totally different approach to this, that I don’t see yet? I’ try to make it clearer, what I’m doing.
The context is a level editor, where I load my level data from ajson file, edit it in game, and write it back as json to disk. So I need to change the data at a particular point in the nested structure.
My normal approach to a problem like this is (if there is not method to write something deep inside a structure), recursively getting the data from via key in a dict and write new data to the last key. The keys are defined by a path. Let’s say I have the following json/dict
To write “fizz” into the “bar” key of the “foo” object I would call set_level_data(NodePath("foo/bar/baz/bli"), "fizz"). The func would than work like this:
segment = _my_data
# I write out the loop here, In every iteration I get the next part of the path
segment = segment["foo"]
segment = segment["bar"]
segment = segment["baz"]
segment["bli"] = "fizz"
This is the working version, with the applied fix that I mentioned in the comment above.
I hope this makes it clearer what I want to do, why and how.