Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: hasField function for use with structs #401

Open
mbezhanov opened this issue Jul 11, 2024 · 1 comment
Open

feat: hasField function for use with structs #401

mbezhanov opened this issue Jul 11, 2024 · 1 comment

Comments

@mbezhanov
Copy link

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!

@42atomys
Copy link

42atomys commented Aug 17, 2024

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants