golang templates processing and generics - templates

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.

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

Related

How to parse a template that has variables from a formatted string?

I have a struct with two fields. The Msg string will be fmt.Sprintf("%s %s"), and I need to parse templates to those two variables in the string
type DataDB struct {
ID int
Msg string
}
Trying to parse template here, and the expected result should be -
Hello Justin, my name is abc
var name = "justin"
var msg = "abc"
justin := DataDB{ID: 1, Msg: fmt.Sprintf("%s %s", name, msg)}
s := []DataDB{justin}
tpl, err := template.New("msgs").Parse(` {{range .}}
Hello {{.name}}, my name is {{.msg}}
{{end}}
`)
if err != nil {
panic(err)
}
tpl.Execute(os.Stdout, s)
First of all, your DataDB struct doesn't have a name field, so you won't be able to reference it in your template.
Update your template to be the following:
type DataDB struct {
ID int
Name string
Msg string
}
and then set a name in your declaration
justin := DataDB{ID: 1, Name: name, Msg: msg}
Now you can reference the .Name field in your template
tpl, err := template.New("msgs").Parse(`
{{range .}}
Hello {{.Name}}, my name is {{.Msg}}
{{end}}
`)
Finally, when referencing fields in templates, the fields need to be exported, so they should all start with an upper case letter. See my version of the DataDB struct above.
EDIT, you don't need the fmt.Sprintf(...) now.
Go playground
It seems your Msg contains Name + " " + Message, so you need to split it on " ".
Go templates are logicless. But you can use helper functions to add logic.
For example:
type DataDB struct {
ID int
Msg string
}
func (d *DataDB) Name() string {
return strings.SplitN(d.Msg, " ", 2)[0]
}
func (d *DataDB) Message() string {
res := strings.SplitN(d.Msg, " ", 2)
if len(res) < 2 {
return ""
}
return res[1]
}
And use them in the template like this:
{{range .}}
Hello {{.Name}}, my name is {{.Message}}
{{end}}
Working example
Note: the code above is slightly inefficient because it does the splitting twice, so it might be better to create a separate struct just for display purposes and fill it before rendering the template.

Golang template variable isset

I have created a function to check if a variable is defined:
fm["isset"] = func(a interface{}) bool {
if a == nil || a == "" || a == 0 {
fmt.Println("is not set")
return false
}
fmt.Println("is set")
return false
}
tmpl := template.Must(template.New("").Funcs(fm).ParseFiles("templates/header.html"))
err := tmpl.ExecuteTemplate(w, "header", templateData)
In the template I have:
{{ if isset .Email }}
email is set
{{ end }}
This function works if the variable is contained by the templateData (which is a custom struct that contains a map and a string), but it gives me an error if the variable doesn't exist.
The error is:
executing "header" at <.Email>: can't evaluate field Email in type base.customData
In my case "base.go" is the handler and "customData" is defined by: type customData struct{..}.
I want to be able to reuse templates and to display some sections only if some variables are sent from the handler. Any idea how can I implement a variable isset check on the template side?
I also tried using: {{ if .Email}} do stuff {{ end }} but this also gives me the same error.
Any idea?
The recommended way
First, the recommended way is not to rely on whether a struct field exists. Of course there might be optional parts of the template, but the condition to decide whether to render a part should rely on fields that exist in all cases.
The issue, and avoiding it using a map
If the type of the template data is a struct (or a pointer to a struct) and there is no field or method with the given name, the template engine returns an error for that.
You could easily get rid of this error if you were to use a map, as maps can be indexed with keys they don't contain, and the result of that index expression is the zero value of the value type (and not an error).
To demonstrate, see this example:
s := `{{if .Email}}Email is: {{.Email}}{{else}}Email is NOT set.{{end}}`
t := template.Must(template.New("").Parse(s))
exec := func(name string, param interface{}) {
fmt.Printf("\n%s:\n ", name)
if err := t.Execute(os.Stdout, param); err != nil {
fmt.Println("Error:", err)
}
}
exec("Filled map", map[string]interface{}{"Email": "as#as"})
exec("Empty map", map[string]interface{}{})
exec("Filled struct", struct {
Email string
}{Email: "as#as.com"})
exec("Empty struct", struct{}{})
Output (try it on the Go Playground):
Filled map:
Email is: as#as
Empty map:
Email is NOT set.
Filled struct:
Email is: as#as.com
Empty struct:
Error: template: :1:5: executing "" at <.Email>: can't evaluate field Email in type struct {}
Sticking to struct and providing "isset"
If you must or want to stick to a struct, this "isset" can be implemented and provided, I'll call it avail().
This implementation uses reflection, and in order to check if the field given by its name exists (is available), the (wrapper) data must also be passed to it:
func avail(name string, data interface{}) bool {
v := reflect.ValueOf(data)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return false
}
return v.FieldByName(name).IsValid()
}
Example using it:
s := `{{if (avail "Email" .)}}Email is: {{.Email}}{{else}}Email is unavailable.{{end}}`
t := template.Must(template.New("").Funcs(template.FuncMap{
"avail": avail,
}).Parse(s))
exec := func(name string, param interface{}) {
fmt.Printf("\n%s:\n ", name)
if err := t.Execute(os.Stdout, param); err != nil {
fmt.Println("Error:", err)
}
}
exec("Filled struct", struct {
Email string
}{Email: "as#as.com"})
exec("Empty struct", struct{}{})
Output (try it on the Go Playground):
Filled struct:
Email is: as#as.com
Empty struct:
Email is unavailable.
If you don't send data containing a variable to the template but you use {{ if .Variable }} you will get the error:
executing "templatename" at <.Variable>: can't evaluate field Variable in type handler.Data
My solution was to send the "Email" variable as a boolean (false) in order to pass the {{ if .Email }} function check. But this is a short term solution that I don't like.
I was inspired by: https://stackoverflow.com/a/31527618/1564840. In that example they show different HTML for authenticated and non-authenticated users. You will see that in both cases they send the "Logged" variable. Try removing that variable from the struct and execute the function. You will receive the error that I mentioned above.
The simple way of doing:
{{ if .Email }}
is to use index:
{{ if index . "Email" }}

How to call Go function from pongo template

I need to create JSON data using few keys of map and need to incorporate into html generated. I am using pongo2 library and want to write custom filter to achieve the same.
<script> {{ CategoryMapping|MycustomFilter }} </script>
and coded custom filter like below.
func init() {
pongo2.RegisterFilter("superfilter", GetCategoryJsonData)
}
func GetCategoryJsonData(CatAttributeMapping *map[string]interface{}, param *int) (*string, *error) {
.....
}
But I am getting below error.
src/util/TemplateFilters.go:10: cannot use GetCategoryJsonData (type func(*int, *int) (*string, *error)) as type pongo2.FilterFunction in argument to pongo2.RegisterFilter
I am following below documentation - https://godoc.org/github.com/flosch/pongo2#FilterFunction
I am new to go and unable to understand what wrong I am doing here. Please guide me for the same.
The problem is that your filter function does not accept or return the right types to match what pongo2 is requiring. Let's walk through the docs and see what they want.
First, take a look at the godoc for RegisterFilterFunction. It says
func RegisterFilter(name string, fn FilterFunction)
This is in the pongo2 package so you should read this as RegisterFilter is a function that accepts two arguments and returns no values. The first argument name is of the builtin type string and the second argument fn is of the type pongo2.FilterFunction. But what is a pongo2.FilterFunction? Well clicking on it we see further down in the doc
type FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
In Go you can make your own types based on any other types including functions. So what pongo2 has done is to create a named type called FilterFunction that is any func which accepts two arguments (both of type *pongo2.Value) and returns two values (one of type *pongo2.value and one of type *pongo2.Error).
To bring it all together we would do something like this:
package main
import (
"fmt"
"log"
"strings"
"github.com/flosch/pongo2"
)
func init() {
pongo2.RegisterFilter("scream", Scream)
}
// Scream is a silly example of a filter function that upper cases strings
func Scream(in *pongo2.Value, param *pongo2.Value) (out *pongo2.Value, err *pongo2.Error) {
if !in.IsString() {
return nil, &pongo2.Error{
ErrorMsg: "only strings should be sent to the scream filter",
}
}
s := in.String()
s = strings.ToUpper(s)
return pongo2.AsValue(s), nil
}
func main() {
tpl, err := pongo2.FromString("Hello {{ name|scream }}!")
if err != nil {
log.Fatal(err)
}
// Now you can render the template with the given
// pongo2.Context how often you want to.
out, err := tpl.Execute(pongo2.Context{"name": "stack overflow"})
if err != nil {
log.Fatal(err)
}
fmt.Println(out) // Output: Hello STACK OVERFLOW!
}

Concrete range example

The Go documentation on the text/template package is so abstract that I'm having trouble figuring out how to actually range over a slice of objects. Here's my attempt so far (This outputs nothing for me):
package main
import (
"os"
templ "text/template"
)
type Context struct {
people []Person
}
type Person struct {
Name string //exported field since it begins with a capital letter
Senior bool
}
func main() {
// Range example
tRange := templ.New("Range Example")
ctx2 := Context{people: []Person{Person{Name: "Mary", Senior: false}, Person{Name: "Joseph", Senior: true}}}
tRange = templ.Must(
tRange.Parse(`
{{range $i, $x := $.people}} Name={{$x.Name}} Senior={{$x.Senior}} {{end}}
`))
tRange.Execute(os.Stdout, ctx2)
}
The range is correct. The problem is that the Context people field is not exported. The template package ignores unexported fields. Change the type definition to:
type Context struct {
People []Person // <-- note that People starts with capital P.
}
and the template to:
{{range $i, $x := $.People}} Name={{$x.Name}} Senior={{$x.Senior}} {{end}}
playground

convert interface{} to certain type

I am developing web service that will receive JSON. Go converts types too strict.
So I did following function to convert interface{} in bool
func toBool(i1 interface{}) bool {
if i1 == nil {
return false
}
switch i2 := i1.(type) {
default:
return false
case bool:
return i2
case string:
return i2 == "true"
case int:
return i2 != 0
case *bool:
if i2 == nil {
return false
}
return *i2
case *string:
if i2 == nil {
return false
}
return *i2 == "true"
case *int:
if i2 == nil {
return false
}
return *i2 != 0
}
return false
}
I believe that function is still not perfect and I need functions to convert interface{} in string, int, int64, etc
So my question: Is there library (set of functions) in Go that will convert interface{} to certain types
UPDATE
My web service receive JSON. I decode it in map[string]interface{} I do not have control on those who encode it.
So all values I receive are interface{} and I need way to cast it in certain types.
So it could be nil, int, float64, string, [...], {...} and I wish to cast it to what it should be. e.g. int, float64, string, []string, map[string]string with handling of all possible cases including nil, wrong values, etc
UPDATE2
I receive {"s": "wow", "x":123,"y":true}, {"s": 123, "x":"123","y":"true"}, {a:["a123", "a234"]}, {}
var m1 map[string]interface{}
json.Unmarshal(b, &m1)
s := toString(m1["s"])
x := toInt(m1["x"])
y := toBool(m1["y"])
arr := toStringArray(m1["a"])
objx package makes exactly what you want, it can work directly with JSON, and will give you default values and other cool features:
Objx provides the objx.Map type, which is a map[string]interface{}
that exposes a powerful Get method (among others) that allows you to
easily and quickly get access to data within the map, without having
to worry too much about type assertions, missing data, default values
etc.
This is a small example of the usage:
o := objx.New(m1)
s := o.Get("m1").Str()
x := o.Get("x").Int()
y := o.Get("y").Bool()
arr := objx.New(m1["a"])
A example from doc working with JSON:
// use MustFromJSON to make an objx.Map from some JSON
m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
// get the details
name := m.Get("name").Str()
age := m.Get("age").Int()
// get their nickname (or use their name if they
// don't have one)
nickname := m.Get("nickname").Str(name)
Obviously you can use something like this with the plain runtime:
switch record[field].(type) {
case int:
value = record[field].(int)
case float64:
value = record[field].(float64)
case string:
value = record[field].(string)
}
But if you check objx accessors you can see a complex code similar to this but with many case of usages, so i think that the best solution is use objx library.
Fast/Best way is 'Cast' in time execution (if you know the object):
E.g.
package main
import "fmt"
func main() {
var inter (interface{})
inter = "hello"
var value string
value = inter.(string)
fmt.Println(value)
}
Try here
I came here trying to convert from interface{} to bool and Reflect gave me a clean way to do it:
Having:
v := interface{}
v = true
The solution 1:
if value, ok := v.(bool); ok {
//you can use variable `value`
}
The solution 2:
reflect.ValueOf(v).Bool()
Then reflect offers a function for the Type you need.