sprig icon indicating copy to clipboard operation
sprig copied to clipboard

feat: `hasField` function for use with structs

Open mbezhanov opened this issue 1 year ago • 1 comments

Recently worked on a project that used generics in a similar fashion:

type A[T any] struct {
	Metadata T
}

type B struct {
	Foo string
}
type C struct {
	Bar string
}

Then, similar objects were being passed to a template for rendering:

ab := &A[B]{Metadata: B{Foo: "Lorem"}}
ac := &A[C]{Metadata: C{Bar: "Ipsum"}}

A section in the template had to look slightly different depending on the type of metadata being passed in, which I solved by using a custom hasField method:

{{if hasField $.Metadata "Foo"}}
  We have Foo.
{{else}}
  We have no Foo.
{{end}}

The hasField method comes from this StackOverflow thread: Field detection in Go HTML template and looks like this:

func hasField(v interface{}, name string) bool {
	rv := reflect.ValueOf(v)
	if rv.Kind() == reflect.Ptr {
		rv = rv.Elem()
	}
	if rv.Kind() != reflect.Struct {
		return false
	}
	return rv.FieldByName(name).IsValid()
}
Full code example
package main

import (
	"log"
	"os"
	"reflect"
	"text/template"
)

type A[T any] struct {
	Metadata T
}

type B struct {
	Foo string
}
type C struct {
	Bar string
}

func main() {
	ab := &A[B]{Metadata: B{Foo: "Lorem"}}
	ac := &A[C]{Metadata: C{Bar: "Ipsum"}}
	s := `
{{if hasField $.Metadata "Foo"}}
We have Foo.
{{else}}
We have no Foo.
{{end}}
`
	f := template.FuncMap{"hasField": hasField}
	t, err := template.
		New("test").
		Funcs(f).
		Parse(s)

	if err != nil {
		log.Fatal(err)
	}
	_ = t.Execute(os.Stdout, ab)
	_ = t.Execute(os.Stdout, ac)
}

func hasField(v interface{}, name string) bool {
	rv := reflect.ValueOf(v)
	if rv.Kind() == reflect.Ptr {
		rv = rv.Elem()
	}
	if rv.Kind() != reflect.Struct {
		return false
	}
	return rv.FieldByName(name).IsValid()
}

Would you approve a PR that adds a hasField method to Sprig, or is that something you prefer to keep separate from the main library?

I'd be happy to open a PR!

mbezhanov avatar Jul 11 '24 13:07 mbezhanov

Hi @mbezhanov, as sprig seams to be not maintained actually, we decide to create a fork to go forward.

You can go forward by creating and reference this initial issue on https://github.com/go-sprout/sprout and you can create a PR for sure in the registry reflect.

Thanks for your understand and your time ! 🌱

Sugar code snippets:

rv := reflect.ValueOf(v)
  if rv.Kind() == reflect.Ptr {
  	rv = rv.Elem()
  }

As the same feature of reflect.Indirect, I lets you check that :)

42atomys avatar Aug 17 '24 15:08 42atomys