Iterate through map in Go text template - go

I have a map of values that looks like this:
vals := map[string]interface{}{"foo": 1, "bar": 2, "baz": 7}
data := map[string]interface{}{"bat": "obj", "values": vals}
What should my template look like to generate the following string (note the correct comma usage)?
SET obj.foo=1, obj.bar=2, obj.baz=7
I started with this as my template:
SET {{range $i, $v := .values}} {{.bat}}.{{$i}}={{$v}},{{end}}
But that just prints out
SET
And even if that did work, the commas would be incorrect. I then tried to use a custom function to format the map, but I couldn't get the template to ever call my function. None of the following seemed to work:
SET {{.MyFunction .values}}
SET {{call .MyFunction .values}}
SET {{call MyFunction .values}}
when MyFunction was defined as:
func MyFunction(data map[string]interface{}) string {
fmt.PrintLn('i was called!')
return "foo"
}
And I'm executing the templates using a helper function that looks like this:
func useTemplate(name string, data interface{}) string {
out := new(bytes.Buffer)
templates[name].Execute(out, data)
return string(out.Bytes())
}
Thanks!

This will get you pretty close:
SET {{range $key, $value := $.values}}{{$.bat}}.{{$key}}={{$value}} {{end}}
rendering as:
SET obj.bar=2 obj.baz=7 obj.foo=1
Unfortunately, I don't think there's any simple way to have the commas added in between the values due to how the range action iterates on maps (there's no numeric index). That said, the template packages were meant to be easily extensible so you can have less logic in your templates and more logic in Go itself, so it's easy enough to code a helper function in Go and make it available to your templates.
If you're happy to go that extra mile, then the template becomes much simpler, and also more efficient. The function can look like this:
func commaJoin(prefix string, m map[string]interface{}) string {
var buf bytes.Buffer
first := true
for k, v := range m {
if !first {
buf.WriteString(", ")
}
first = false
buf.WriteString(prefix)
buf.WriteByte('.')
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(fmt.Sprint(v))
}
return buf.String()
}
and your template would look like:
SET {{$.values | commaJoin $.bat}}
Here is a working example with this logic:
http://play.golang.org/p/5lFUpFCzZm

Related

Variadic parameters in Golang templates

Is there any way to "spread" parameters in Go templates?
In my code I have a function that returns the properties of a list of structs as a string slice
"colStr": func(col string, a ...interface{}) []string {
s := make([]string, len(a))
for i, v := range a {
if structs.IsStruct(v) {
s[i] = db.Str(structs.Map(v)[col])
}
}
return s
},
But how do I pass to this function? In the template itself, I have something that looks like this
{{list (colStr `Number` .QuoteProofs...) `&`}}{{end}}
But that gives me
template: HTML:17: unexpected <.> in operand
Is there any way to do this with templates?

Cannot Range Over List Type Interface {} In Function Using Go

Cannot Range Over List Type Interface {} In Function Using Go.
for me is important then i execute for in a function.
How can fix?
package main
import (
"fmt"
)
type MyBoxItem struct {
Name string
}
type MyBox struct {
Items []MyBoxItem
}
func (box *MyBox) AddItem(item MyBoxItem) []MyBoxItem {
box.Items = append(box.Items, item)
return box.Items
}
func PrintCustomArray(list interface{}) interface{} {
//items := reflect.ValueOf(list)
for _, v := range list {
fmt.Println(v.Key,v.Value)
}
return 0
}
func main() {
items := []MyBoxItem{}
item := MyBoxItem{Name: "Test Item 1"}
box := MyBox{items}
box.AddItem(item)
fmt.Println((box.Items))
PrintCustomArray(box.Items)
}
https://play.golang.org/p/ZcIBLMliq3
Error : cannot range over list (type interface {})
How can fix?
Note
The answer below describes, in broad strokes, 2 possible approaches: using interfaces, and using specific types. The approach focusing on interfaces is mentioned for completeness sake. IMHO, the case you've presented is not a viable use-case for interfaces.
Below, you'll find a link to a playground example that uses both techniques. It should be apparent to anyone that the interface approach is too cumbersome if for this specific case.
Quite apart from the fact that you don't really seem to be too familiar with how loops work in go (v.Key and v.Value are non-existent fields for example), I'll attempt to answer your question.
You are passing a list to your function, sure enough, but it's being handled as an interface{} type. That means your function accepts, essentially, any value as an argument. You can't simply iterate over them.
What you can do is use type assertions to convert the argument to a slice, then another assertion to use it as another, specific interface:
type Item interface{
key() string
val() string
}
func (i MyBoxItem) key() string {
return i.Key
}
func (i MyBoxItem) val() string {
return i.Value
}
func PrintCustomArray(list interface{}) error {
listSlice, ok := list.([]interface{})
if !ok {
return fmt.Errorf("Argument is not a slice")
}
for _, v := range listSlice {
item, ok := v.(Item)
if !ok {
return fmt.Errorf("element in slice does not implement the Item interface")
}
fmt.Println(item.key(), item.val())
}
return nil
}
But let's be honest, a function like this only works if a slice is passed as an argument. So having that first type assertion in there makes no sense whatsoever. At the very least, changing the function to something like this makes a lot more sense:
func PrintCustomArray(list []interface{})
Then, because we're not expecting an array as such, but rather a slice, the name should be changed to PrintCustomSlice.
Lastly, because we're using the same type assertion for every value in the slice, we might as well change the function even more:
// at this point, we'll always return 0, which is pointless
// just don't return anything
func PrintCustomSlice(list []Item) {
for _, v := range list {
fmt.Println(v.key(), v.val())
}
}
The advantages of a function like this is that it can still handle multiple types (all you have to do is implement the interface). You don't need any kind of expensive operations (like reflection), or type assertions.
Type assertions are very useful, but in a case like this, they merely serve to hide problems that would otherwise have resulted in a compile-time error. Go's interface{} type is a very useful thing, but you seem to be using it to get around the type system. If that's what you want to achieve, why use a typed language in the first place?
Some closing thoughts/remarks: If your function is only going to be used to iterate over specific "thing", you don't need the interfaces at all, simply specify the type you're expecting to be passed to the function in the first place. In this case that would be:
func PrintCustomSlice(list []MyBoxItem) {
for _, v := range list {
fmt.Println(v.Key, v.Value)
}
}
Another thing that I've noticed is that you seem to be exporting everything (all functions, types, and fields start with a capital letter). This, in go, is considered bad form. Only export what needs to be public. In the main package, that usually means you're hardly export anything.
Lastly, as I mentioned at the start: you don't seem to have a firm grasp on the basics just yet. I'd strongly recommend you go through the interactive tour. It covers the basics nicely, but shows you the features of the language at a decent pace. It doesn't take long, and is well worth taking a couple of hours to complete
Playground demo
It's possible to implement PrintCustomArray using the reflect package, but most experienced Go programmers will write a simple for loop:
for _, i := range box.Items {
fmt.Println("Name:", i.Name)
}
https://play.golang.org/p/RhubiCpry0
You can also encapsulate it in a function:
func PrintCustomArray(items []MyBoxItem) {
for _, i := range items {
fmt.Println("Name:", i.Name)
}
}
https://play.golang.org/p/c4EPQIx1AH
Here since you are returning box.Items from AddItem(), Items is of the type []MyBoxItem , so list should be of type []MyBoxItem .Moreover you are returning 0 in PrintCustomArray and the return type you have set is {}interface.
func PrintCustomArray(list []MyBoxItem) {
//items := reflect.ValueOf(list)
for i, v := range list {
fmt.Println(i, v)
}
//return 0
}
Again, MyBoxItem struct has only one variable named Name so v.key v.value won't make any sense.
This is what the proper code should look like https://play.golang.org/p/ILoUwEWv6Y .
You need to clear your understanding about interfaces in go. This might help https://golang.org/doc/effective_go.html#interfaces_and_types .

How to return unique elements in an array using Go's text/template package?

I am new to Go and I am struggling trying to figure out a way to return unique variables from an array in Go templating language. This is to configure some software and I do not have access to the source to change the actual program only the template.
I have knocked up an example in the Go playground:
https://play.golang.org/
package main
import "os"
import "text/template"
func main() {
var arr [10]string
arr[0]="mice"
arr[1]="mice"
arr[2]="mice"
arr[3]="mice"
arr[4]="mice"
arr[5]="mice"
arr[6]="mice"
arr[7]="toad"
arr[8]="toad"
arr[9]="mice"
tmpl, err := template.New("test").Parse("{{range $index, $thing := $}}The thing is: {{$thing}}\n{{end}}")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, arr)
if err != nil { panic(err) }
}
Right now this returns:
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: mice
The thing is: toad
The thing is: toad
The thing is: mice
What I am trying to do is craft a template that from the input array filters duplicates and only returns:
The thing is: mice
The thing is: toad
I am really stuck as I know virtually no go and struggle to find any array manipulation methods in the docs. Any one have any tips?
Addenium
Sorry for not being clear I wrote this question on the bus on the way to work.
I don't have access to any go code outside the template. I have a template I can edit and within that template I have an array that may or may not have multiple values and I need to print them once.
I appreciate this is not how templates are meant to work but if there is some dirty way to do this it would save me several days work.
You can create your own functions for the template via template.FuncMap:
arr := []string{
"mice",
"mice",
"mice",
"mice",
"mice",
"mice",
"mice",
"toad",
"toad",
"mice",
}
customFunctions := template.FuncMap{"unique" : unique}
tmpl, err := template.New("test").Funcs(customFunctions).Parse("{{range $index, $thing := unique $}}The thing is: {{$thing}}\n{{end}}")
Where unique is defined as:
func unique(e []string) []string {
r := []string{}
for _, s := range e {
if !contains(r[:], s) {
r = append(r, s)
}
}
return r
}
func contains(e []string, c string) bool {
for _, s := range e {
if s == c {
return true
}
}
return false
}
Output:
The thing is: mice
The thing is: toad
(It might be better to use a map .. but this gives you the basic idea)
That said - have you considered filtering this outside of the template? That would make things nicer for you.. then you can just iterate over the actual slice within the template.
Working sample: https://play.golang.org/p/L_8t10CpHW
The template can acces a particular custom function http://golang.org/pkg/text/template/#FuncMap. This allows your own logic to be called from within the template.
There is a comprehensive example in the docs which I wont repeat here. The key line is setting up a funcion Map and providing this to the template:
tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText)
Then can be accessed within the template.
{{myCustomFuction .}}
Since now it is established that it will take more go code in the form of a mapped function to achieve, I thought I would share a thought that might get the job done. If you have control of which template that the 'go' program you are not able to modify runs, then you could make several passes. I am also assuming you are on linux. Something like this:
goexe 'first template' // this writes to 'text file'
cat textfile | sort | uniq > 'text file'
goexe 'second template' // your desired output

How do I create a slice of one item in Go?

Let's say a function takes a slice of strings:
func Join(strs []string) {
...
}
I have a single string:
a := "y'all ain't got the honey nut?"
How can I convert that string into a slice?
You can create a slice of one item using the following convention:
a := "y'all ain't got the honey nut?"
singleItemArray := []string{a}
strings.Join(singleItemArray);
The actual answer to your question is as simple as []string{"string"}, as miltonb said.
But what I wanted to point out is how easy it is to write and use a variadic function in Go, a function with a variable number of arguments.
You can change signature of your function to F(a ...string). Then, a is slice in the function F, and you can call it like F("a") and F("a", "b"). And when you actually have a slice or array, you can pass it to F by calling F(a...).
Not sure if this syntax fits your job, but I wanted to let you know about it as an option.
The question as phrased actually references Arrays and Slices. The question text is about an array and the code is illustrating using a slice. Therefore there two questions are implied; pass a single item slice, and pass a single item array.
An array: var a [1]string
A slice: var s []string
Passing a single item slice to the function:
func SliceFunc( slc []string) {
fmt.Println(slc)
}
func main() {
a := "stringy"
SliceFunc( []string{a} )
// or an actual array to the same function
b := [...]string { "thingy" }
SliceFunc( []string{b[0] )
}
Passing a single item array to the function.
Here there is an issue, as an array has a fixed length and as a parameter to a function it cannot accept different length arrays so we are left with working function which has limited flexibility:
func ArrayFunc( arr [1]string) {
fmt.Println(slc)
}
func main() {
var a [1]string
a[0] = "stringy"
ArrayFunc( a )
}
It seems that as a generalization sticking to slices is a more flexible solution.
(If you would like more on Slices and Arrays here one blog by Andrew Gerrand covering go slices usage and internals.)
You can utilize append or make:
package main
import "fmt"
func main() {
{
var a []string
a = append(a, "north")
fmt.Println(a)
}
{
a := make([]string, 1)
a[0] = "north"
fmt.Println(a)
}
}
https://golang.org/pkg/builtin

Writing generic data access functions in Go

I'm writing code that allows data access from a database. However, I find myself repeating the same code for similar types and fields. How can I write generic functions for the same?
e.g. what I want to achieve ...
type Person{FirstName string}
type Company{Industry string}
getItems(typ string, field string, val string) ([]interface{}) {
...
}
var persons []Person
persons = getItems("Person", "FirstName", "John")
var companies []Company
cs = getItems("Company", "Industry", "Software")
So you're definitely on the right track with the idea of returning a slice of nil interface types. However, you're going to run into problems when you try accessing specific members or calling specific methods, because you're not going to know what type you're looking for. This is where type assertions are going to come in very handy. To extend your code a bit:
getPerson(typ string, field string, val string) []Person {
slice := getItems(typ, field, val)
output := make([]Person, 0)
i := 0
for _, item := range slice {
// Type assertion!
thing, ok := item.(Person)
if ok {
output = append(output, thing)
i++
}
}
return output
}
So what that does is it performs a generic search, and then weeds out only those items which are of the correct type. Specifically, the type assertion:
thing, ok := item.(Person)
checks to see if the variable item is of type Person, and if it is, it returns the value and true, otherwise it returns nil and false (thus checking ok tells us if the assertion succeeded).
You can actually, if you want, take this a step further, and define the getItems() function in terms of another boolean function. Basically the idea would be to have getItems() run the function pass it on each element in the database and only add that element to the results if running the function on the element returns true:
getItem(critera func(interface{})bool) []interface{} {
output := make([]interface{}, 0)
foreach _, item := range database {
if criteria(item) {
output = append(output, item)
}
}
}
(honestly, if it were me, I'd do a hybrid of the two which accepts a criteria function but also accepts the field and value strings)
joshlf13 has a great answer. I'd expand a little on it though to maintain some additional type safety. instead of a critera function I would use a collector function.
// typed output array no interfaces
output := []string{}
// collector that populates our output array as needed
func collect(i interface{}) {
// The only non typesafe part of the program is limited to this function
if val, ok := i.(string); ok {
output = append(output, val)
}
}
// getItem uses the collector
func getItem(collect func(interface{})) {
foreach _, item := range database {
collect(item)
}
}
getItem(collect) // perform our get and populate the output array from above.
This has the benefit of not requiring you to loop through your interface{} slice after a call to getItems and do yet another cast.

Resources