Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to set value deep in object #204

Open
kferrone opened this issue Aug 19, 2021 · 2 comments
Open

How to set value deep in object #204

kferrone opened this issue Aug 19, 2021 · 2 comments

Comments

@kferrone
Copy link

I am looking for the ability to arbitrarily set a value deep on a dict using some form of path syntax.

for example:

{
  "a": {
    "b": "foo"
  }
}

Taken I have a variable like this for the path to the value a.b
I want to easily set the value to bar

At this moment I have no idea how to easily do this in starlark without having to split on dot and loop through the keys to build an object for dict.update

@ndmitchell
Copy link
Contributor

If the string is static, x["a"]["b"] = "bar" should work.

If you want to specify it as a.b without breaking it apart, then it seems like you want a feature that isn't available in Starlark. But that's OK, lots of features aren't - you can write a function:

def set_deep(dict, path, val):
    ...

Which does what you want - and now you can extend Starlark with that feature. Generally Starlark is a subset of Python, so if you think this is useful, I think the interesting question would be how do you do it in Python, and can Starlark do it the same way or does it lack something. And my guess is in Python you'd do something very much like set_deep? Or is there a better way in Python?

@kferrone
Copy link
Author

kferrone commented Aug 23, 2021

So after reading the entire spec and also finding out recursive functions are also banned, I actually figured it out. Well I already knew what needed to be done, just wanted to hear there was an easier way. Although this is basically Python, Starlark does also bans imports. So I was unable to import all the fun libraries Python core had to offer like deepSet and deepCopy, ie these are not actually in Python itself.

In my case I am using Starlark with Kustomize.

Just in case someone else out there wants to save a few minutes; here is my painfully manual deepGet and json patch,

# uses a json patch style to patch an objects deep value
# p = { path, op, value }
def patch(p, r):
  ref = r
  parts = p["path"].split("/")
  count = len(parts)
  for i in range(count): # recurse into the resource now to set the patch
    k = parts[i]
    # the first part of this if block is to make sure arrays and objects are initialized correctly
    if not k == parts[-1]: # if k is not the last item in the list
      if not k.isdigit():
        if not k in ref: # we create the structure if the key is not already there
          if parts[i+1].isdigit(): # if the next step is an array accessor
            ref[k] = [] # we need to init the array
          else:
            ref[k] = {}
    # this section is for actually setting the value
    else:
      val = p["value"]
      if k.isdigit():
        ref.insert(int(k), val)
      else:
        # add dict means apply the values to the source
        if type(val) == "dict" and p["op"] == "add":
          ref[k].update(val)
        # everything else basically means replace
        else:
          ref[k] = val
      break
    if k.isdigit():
      k = int(k)
    # unlink the dicts so the replicas are not linked to each other
    if type(ref[k]) == "dict":
      ref[k] = unlink(ref[k])
    if type(ref[k]) == "list":
      ref[k] = list(ref[k])
      for ii in range(len(ref[k])):
        if type(ref[k][ii]) == "dict":
          ref[k][ii] = dict(ref[k][ii])
    ref = ref[k]

# Get a value deep within an object using a path
# example: spec/containers/0/name
def deepGet(path, r):
  ref = r
  parts = path.split("/")
  count = len(parts)
  for i in range(count): # for each index in the parts
    k = parts[i]
    # if the part is a number then the next step will access an array
    if k.isdigit(): 
      k = int(k)
    else:
      if not k in ref: # make sure the key actually exists
        fail("The value at",path,"with key",k,"was not found on",r["kind"]+"/"+r["metadata"]["name"])
    if type(ref[k]) == "dict":
      ref = unlink(ref[k])
    elif type(ref[k]) == "list":
      ref = list(ref[k])
    else:
      ref = ref[k]
  return ref

# quick helper to decide how to unlink a value from its source
# this is only a shallow copy, as good as it's going to get
def unlink(r):
  for k, v in r.items():
    if type(v) == "dict":
      r[k] = dict(v)
    elif type(v) == "list":
      r[k] = list(v)
  return r

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants