How to access dict entries via reference

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: 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?

:bust_in_silhouette: Reply From: Bernard Cloutier

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. :slight_smile:

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

thomash | 2020-10-19 04:01

:bust_in_silhouette: Reply From: CharlesMerriam

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

_my_data = {
  "foo": {
    "bar": {
      "baz": {
        "bli" = "blub"
      }
    }
  }
}

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.

thomash | 2020-10-19 04:17