Sankar
Sankar

Reputation: 6541

golang templates processing and generics

I have two golang html templates, as follows:

var m map[string]string
m = make(map[string]string)

m["First"] = `<html>
<body>First template type {{.First}}
</html>`

m["Second"] = `<html>
<body>Second template type {{.SecondF1}} {{.SecondF2}}
</html>`

The first html template takes only one argument, named First whereas the second template needs two arguments, named SecondF1 and SecondF2.

Now I have a struct which has two fields, one for receiving a template name and another for receiving the template arguments.

type tmplReceiver struct {
    TmplName string
    TmplArgs string // Receives JSON string
}

Now, examples of instances for the above structs could be:

var i, j tmplReceiver

i.TmplName = "First"
i.TmplArgs = `{"Field1": "First Template Argument"}`

j.TmplName = "Second"
j.TmplArgs = `{
  "SecondF1": "Second template First Argument", 
  "SecondF2": "Second template Second Argument"
}`

Now I can get the Template string by using the map, for example:

tmplStr := m[i.TmplName] (or)
tmplStr := m[j.TmplName]

tmpl, _ = template.New("email").Parse(tmplStr)

However, how do I get the template to be executed for all the possible template types, with a single tmpl.Execute statement. In other words, if I want to have the following code:

var buff bytes.Buffer
if err := tmpl.Execute(&buff, tmplPtr); err != nil {
    log.Fatal(err)
}
log.Println(buff.String())

How do I get the tmplPtr to be valid, irrespective of how many templates I have (First, Second, etc.) and each of these templates can have a variable number of arguments (First has only one arg, whereas Second has two args, etc.)

I do not want to write N different tmpl.Execute statements with an if block for each template name. Is there any other alternative approach to solve this ? Thanks.

Upvotes: 0

Views: 1126

Answers (1)

Vincent van der Weele
Vincent van der Weele

Reputation: 13187

Neither json.Unmarshal nor template.Execute cares about the actual type of the data, this will all be handled at runtime. So you can just parse the json to an interface{} and pass that to your templates. Provided that the json data contains the fields that are expected by the template to which you pass the data, this will just work fine.

Playground link

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "html/template"
)

var templates = map[string]*template.Template{
    "A": template.Must(template.New("A").Parse("{{ .A }}")),
    "B": template.Must(template.New("B").Parse("{{ .B }} and {{ .C.D }}")),
}

type tmplReceiver struct {
    TmplName string
    TmplArgs string
}

func main() {
    receivers := []tmplReceiver{
        tmplReceiver{"A", `{"A": "Value for A"}`},
        tmplReceiver{"B", `{"B": "Value for B", "C": { "D": "Value for D" }}`},
    }

    for _, receiver := range receivers {
        var data interface{}
        json.Unmarshal([]byte(receiver.TmplArgs), &data)

        var buffer bytes.Buffer
        templates[receiver.TmplName].Execute(&buffer, data)
        fmt.Println(buffer.String())
    }
}

Which prints

Value for A

Value for B and Value for D

Upvotes: 0

Related Questions