Masterminds / sprig

Useful template functions for Go templates.
http://masterminds.github.io/sprig/
MIT License
4.07k stars 423 forks source link

feat(dig): accepts string with dot or arrays as "path" parameters #352

Open davinkevin opened 1 year ago

davinkevin commented 1 year ago

Hello 👋 ,

I would like to use the really useful dig function within a range, but because the dig function only accepts "varargs" for path, I'm not able to use it.

With $someStruct equals to:

{
  "a": {
    "b": {
      "c": "value"
    }
  },
  "foo": {
    "bar": {
      "zoo": "value2"
    }
  }
}

First,

    {{- $keysToGet := list
      "foo.bar.zoo"
      "a.b.c"
    -}}
    {{ range $keysToGet }}
      {{ dig . "~" $someStruct }}
    {{ end }}

The result of this one is nil because dig looks for { "a.b.c": "value" } 🙂. So the result is twice ~ instead of value and value2.

Second, with array:

    {{- $keysToGet := list
      "foo.bar.zoo"
      "a.b.c.d.e"
    -}}
    {{ range $keysToGet }}
      {{ dig (splitList "." .) "~" $someStruct }}
    {{ end }}

Here, it's a type cast problem because the first parameter is not a string like expected by the function. I have the following error:

dig (splitList "." .) "~" $savedValues>: error calling dig: interface conversion: interface {} is []string, not string

It could be cool, especially in dynamic env like a range or with to have the ability to provide "path" coming from another variable dynamically

Thank you 😇

traviswt commented 1 year ago

I do the following, not perfect, but works when you can control the context:

func FuncMap() ttemplate.FuncMap {
    f := sprig.TxtFuncMap()
    f["getNested"] = getNested
    return f
}

func getNested(d map[string]interface{}, key string) interface{} {
    t := strings.Split(key, ".")
    for i := 0; i < len(t); i++ {
        if val, ok := d[t[i]]; ok {
            if i+1 == len(t) {
                return val
            } else {
                d = castAndGetMapStringInterface(val)
            }
        }
    }
    panic(fmt.Errorf("the map does not have a match for key %s", key))
}

func castAndGetMapStringInterface(i interface{}) map[string]interface{} {
    m, ok := i.(map[string]interface{})
    if ok {
        return m
    }
    return nil
}

then in your template you can do:

{{getNested .ImageMap "ingress-controller.registry"}}

for struct:

type InterpolateContext struct {
    ImageMap map[string]interface{}
}