Iterate Go map get index

DiCaprio picture DiCaprio · Mar 22, 2017 · Viewed 10k times · Source

In order to use revel's even keyword in templates I would like to get the index of a map entry when iterating with range. Is there any way to do so? My map has the structure:

map[string][]string

Answer

icza picture icza · Mar 22, 2017

You can't do this only with template actions, but you may register a function which provides the necessary help.

You may register a function which returns a function (closure), which alternates its return value whenever called (exactly how "odd" and "even" indices alternate):

func isEven() func() bool {
    e := false
    return func() bool {
        e = !e
        return e
    }
}

I named it isEven() to not collide with ravel's even(). Using it:

func main() {
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "isEven": isEven,
    }).Parse(templ))

    m := map[string]string{
        "a": "A", "b": "B", "c": "C", "d": "D",
    }
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const templ = `{{$e := isEven}}
{{- range $k, $v := . -}}
    [even:{{call $e}}] key={{$k}}; value={{$v}}
{{end}}`

Output (try it on the Go Playground):

[even:true] key=a; value=A
[even:false] key=b; value=B
[even:true] key=c; value=C
[even:false] key=d; value=D

If you want different output for odd and even iterations, you can call $e in an {{if}} action, like this:

const templ = `{{$e := isEven}}
{{- range $k, $v := . -}}
    [{{if call $e}}even{{else}}odd {{end}}] key={{$k}}; value={{$v}}
{{end}}`

Output of this (try it on the Go Playground):

[even] key=a; value=A
[odd ] key=b; value=B
[even] key=c; value=C
[odd ] key=d; value=D

Under the hood

This template action:

{{$e := isEven}}

Creates a new template variable named $e, and its value will be the result (return value) of the isEven() function call. isEven() returns a function value, a closure that has access to a local variable e of type bool. When later you do {{call $e}}, you're not calling the isEven() Go function, but the function it returned (the closure) and is stored in $e. That closure has a reference to the local bool variable e, it is not "freed" until the function returned by isEvent() is accessible.

So whenever you do {{call $e}}, it calls the closure, which "has" an e variable of type bool, whose value is retained between calls of this $e.

If you would call isEvent in the template again, that would return a new function (closure), wrapping a new instance of the local variable e, being independent of the first wrapped variable of the closure returned by the first isEvent() call.