type mcat struct {
ID int
}
type cat struct {
Name string
M mcat
}
func getValue(path string, mcat cat){
//throuth struct path get the value
}
func main(){
mycat := cat{"cat", mcat{1}}
id := getvalue("/M/ID", mycat)
}
Can I do this by reflecting to get a value based on the field name?
You may do what you want with the Value.FieldByName() function. Just range over the parts of the path which may be splitted using strings.Split().
Here's an example:
func getValue(i interface{}, path string) interface{} {
v := reflect.ValueOf(i)
for _, field := range strings.Split(path[1:], "/") {
v = v.FieldByName(field)
}
return v.Interface()
}
func main() {
mycat := cat{"cat", mcat{1}}
id := getValue(mycat, "/M/ID")
fmt.Println(id)
}
It outputs (try it on the Go Playground):
1
Some things to note:
The above solution works for all struct types, not just with cat. Checks if the passed value is a struct or the field exists is omitted.
I cut of the leading / of the path with a slice expression: path[1:] so we don't have to deal with an empty field name inside the loop.
The above getValue() returns the result as an interface{}. If you need the ID as an int, you may use type assertion like this:
var intID int
intID = id.(int)
Also note that it may be nicer / more useful to use a variadic parameter for the path:
func getValue(i interface{}, path ...string) interface{} {
v := reflect.ValueOf(i)
for _, field := range path {
v = v.FieldByName(field)
}
return v.Interface()
}
func main() {
mycat := cat{"cat", mcat{1}}
id := getValue(mycat, "M", "ID")
fmt.Println(id)
}
Output is the same. Try this one on the Go Playground.
Related
I want to manipulate various structs' field which has same name and type programmatically like the following but I have no idea how to put varied structs in a list.
package main
import "fmt"
type A struct {
Cnt int
}
type B struct {
Cnt int
}
func main() {
a := &A{}
b := &B{}
list := []something{
a,
b,
}
for _, item := range list {
item.Cnt++
}
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
}
Declare a common interface for the types. The methods should reflect whatever action you want to executed on the values. I use add here as generalization of increment.
type Cntr interface {
Add(i int)
}
Implement that interface on each type:
func (a *A) Add(i int) { a.Cnt += i }
func (b *B) Add(i int) { b.Cnt += i }
Declare slice of interface type and with values of types *A and *B:
a := &A{}
b := &B{}
list := []Cntr{ // <-- slice of the interface type
a,
b,
}
Increment the counters:
for _, item := range list {
item.Add(1)
}
Print the results:
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
// prints a.Cnt: 1, b.Cnt: 1
Run this playground on the program.
Use the reflect API to get a pointer to a named field in an arbitrary struct type:
func getFieldPtr(v interface{}, name string) interface{} {
return reflect.ValueOf(v).Elem().FieldByName(name).Addr().Interface()
}
Use it like this:
a := &A{}
b := &B{}
list := []interface{}{
a,
b,
}
for _, item := range list {
pcnt := getFieldPtr(item, "Cnt").(*int)
*pcnt++
}
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
https://go.dev/play/p/InVlnv37yqW
I write the code that fills data structures depending on its type. I need to call nested struct function if it exists.
Why I get zero value looking for function while the field is correct?
type (
SomeData struct {
Val NestedType
}
NestedType struct {
V1 string
}
)
func (t *NestedType) FillData(v int) {
t.V1 = fmt.Sprintf("Here is %v", v)
}
func main() {
i := SomeData{}
reflect.ValueOf(&i.Val).MethodByName("FillData").Call([]reflect.Value{reflect.ValueOf(555)})
fmt.Println(i) /// {{I hate 555}}
// BUT!
v := 666
newObj := reflect.New(reflect.TypeOf(SomeData{}))
fVal := newObj.Elem().FieldByName("Val")
fmt.Println( "fVal.NumField():", fVal.NumField()) //fVal.NumField(): 1
f := fVal.MethodByName("FillData")
f.Call([]reflect.Value{reflect.ValueOf(v)}) //panic: reflect: call of reflect.Value.Call on zero Value
}
The method is on the pointer receiver. The value fVal is a NestedType. Call Value.Addr to get a *NestedType:
f := fVal.Addr().MethodByName("FillData")
Run it on the playground.
I am trying to copy the fields from one struct value to another, where they have the same field definitions. I have this program:
package main
import (
"log"
"reflect"
)
func setExistingFields(src interface{}, dst interface{}) {
fields := reflect.TypeOf(src)
values := reflect.ValueOf(src)
num := fields.NumField()
s := reflect.ValueOf(src).Elem()
d := reflect.ValueOf(dst).Elem()
for i := 0; i < num; i++ {
field := fields.Field(i)
value := values.Field(i)
fsrc := s.FieldByName(field.Name)
fdest := d.FieldByName(field.Name)
if fdest.IsValid() && fsrc.IsValid() {
if fdest.CanSet() && fsrc.CanSet() {
fdest.Set(value)
}
}
}
}
// and then we main:
func main() {
src := struct {
Foo string
Bar string
}{
"dog",
"pony",
}
dest := struct{ Foo string; Bar string }{}
setExistingFields(&src, &dest)
log.Println("dest.Foo", dest.Foo)
}
I run that, but I get an error:
reflect: NumField of non-struct type
I can't figure out what that's about.
Here's a playground link:
https://play.golang.org/p/TsHTfAaeKhc
Try this out:
func setExistingFields(src interface{}, dst interface{}) {
srcFields := reflect.TypeOf(src).Elem()
srcValues := reflect.ValueOf(src).Elem()
dstValues := reflect.ValueOf(dst).Elem()
for i := 0; i < srcFields.NumField(); i++ {
srcField := srcFields.Field(i)
srcValue := srcValues.Field(i)
dstValue := dstValues.FieldByName(srcField.Name)
if dstValue.IsValid() {
if dstValue.CanSet() {
dstValue.Set(srcValue)
}
}
}
}
Note that you need to do additional checking if src field value is assignable to dst field type.
Edit: The reason why you are getting that error is because fields at that point is a pointer to a struct. You need to get the actual struct value by using Elem().
This won't work: A struct always gets its "schema" (eg. its fields) during compile time... You cannot add more fields during runtime.
I don't see what your exact use case is, but consider something like map[string]string or even map[string]interface{} to be able to "extend" the content/fields of the thing you are passing around...
Can someone explain why this doesn't work and how can we return slices of interfaces, []interface{}, from functions like showed in the example?
import (
"fmt"
)
func main() {
var test []string
Test(&test)
fmt.Println(test)
}
func Test(t interface{}) {
a := []interface{}{"first", "second"}
fmt.Println(a)
t = a
}
Example of running code can be found here:
https://play.golang.org/p/vcEGHSdWrjv
BTW, this is the func I'm trying to extract data from: https://godoc.org/github.com/mongodb/mongo-go-driver/mongo#Collection.Distinct
Note: the type we expect is not always of type []string, I'm just using string as an example here.
Thanks!
In you example, you are not trying to "return" a slice but rather you seem to be looking to modify the argument to point to a new slice of strings.
The way you are doing it does not work cause in Go, arguments are passed by value.
When you do this:
t = a
t is a copy of the &test you are sending to the function as an argument.
So modifying t does not change your test variable.
You need to pass in the address of a pointer in order to be able to modify what the pointer points to.
Try this way:
func main() {
var test *[]string
Test(&test)
fmt.Println(*test)
}
func Test(t interface{}) {
a := []string{"first", "second"}
fmt.Println(a)
*t.(**[]string) = &a
}
Output:
[first second]
[first second]
Playground: https://play.golang.org/p/tliMrmliykp
Because an []string and a []interface{} are different types, you cannot assign one to the other.
You must copy the slice to convert []interface{} to a slice of some specific type. If you know that the []interface{} always contains string values, then use the following:
func stringSlice() []string {
a := []interface{}{"first", "second"} // query result
fmt.Println(a)
result := make([]string, len(a))
for i := range a {
var ok bool
result[i], ok = a[i].(string)
if !ok {
// handle error with unexpected type
}
}
return result
}
If the result can have arbitrary element types, then use reflection to copy the slice:
func anySlice(result interface{}) {
a := []interface{}{"first", "second"} // query result
slice := reflect.ValueOf(result).Elem()
elementType := slice.Type().Elem()
for _, v := range a {
rv := reflect.ValueOf(v)
if !rv.Type().AssignableTo(elementType) {
// handle error with unexpected type
}
slice.Set(reflect.Append(slice, rv))
}
}
Use it like this:
var s []string
anySlice(&s)
playground example
I found this question with this great answers:
How to find a type of a object in Golang?
I played around with the answer and tried to get the name of a struct in the same way:
package main
import (
"fmt"
"reflect"
)
type Ab struct {
}
func getType(myvar interface{}) string {
return reflect.TypeOf(myvar).Name()
}
func main() {
fmt.Println("Hello, playground")
tst := "string"
tst2 := 10
tst3 := 1.2
tst4 := new(Ab)
fmt.Println(getType(tst))
fmt.Println(getType(tst2))
fmt.Println(getType(tst3))
fmt.Println(getType(tst4))
}
Go playground: http://play.golang.org/p/tD8mygvETH
But the output is:
Hello, playground
string
int
float64
Program exited.
Expected output would be:
Hello, playground
string
int
float64
Ab
Program exited.
I tried to figure out by reading the documentation but didn't find the issue about that. So, sorry for the very general question, but:
What's the reason, reflect.TypeOf().Name() does not work with (this) struct(s)?
In your example you pass a value of pointer type (*Ab), not a struct type.
Sticking to Type.Name()
If it is not a pointer, Type.Name() will properly return Ab. In case of pointer if you still want the struct's name, you can use Type.Elem() to get the element's type:
func getType(myvar interface{}) string {
if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr {
return "*" + t.Elem().Name()
} else {
return t.Name()
}
}
Testing it:
tst4 := Ab{}
tst5 := new(Ab)
fmt.Println(getType(tst4))
fmt.Println(getType(tst5))
Output (try your modified example on the Go Playground):
Ab
*Ab
Note:
Note that as Type.Name() does not resolve pointers, it would not work if the value passed is a pointer to pointer, e.g. **Ab, while as Type.String() automatically resolves pointers, would work in this case too.
We can easily make our getType() function to work with **Ab too (or with any depth of pointers):
func getType(myvar interface{}) (res string) {
t := reflect.TypeOf(myvar)
for t.Kind() == reflect.Ptr {
t = t.Elem()
res += "*"
}
return res + t.Name()
}
Calling it with values:
tst4 := Ab{}
tst5 := new(Ab)
tst6 := &tst5 // type of **Ab
tst7 := &tst6 // type of ***Ab
Output (try it on the Go Playground):
Ab
*Ab
**Ab
***Ab
Using Type.String()
A simpler and better approach would be to use Type.String() instead of Type.Name() which automatically handles pointers and also includes package name. E.g.:
func getType(myvar interface{}) string {
return reflect.TypeOf(myvar).String()
}
For the modified example it outputs:
string
int
float64
main.Ab
*main.Ab
Try this variant on the Go Playground.
fmt has a cool %T tag as well
package main
import (
"fmt"
"net/http"
)
type Potato struct {
}
func main() {
fmt.Printf("I have a %T, an %T and a %T\n", Potato{}, http.StatusMultipleChoices, &http.Response{})
}
outputs I have a main.Potato, an int and a *http.Response
https://play.golang.org/p/6z7_0BSitm
The problem is new returns pointer, following should get the desired result.
package main
import (
"fmt"
"reflect"
)
type Ab struct {
}
func getType(myvar interface{}) {
valueOf := reflect.ValueOf(myvar)
if valueOf.Type().Kind() == reflect.Ptr {
fmt.Println(reflect.Indirect(valueOf).Type().Name())
} else {
fmt.Println(valueOf.Type().Name())
}
}
func main() {
fmt.Println("Hello, playground")
tst := "string"
tst2 := 10
tst3 := 1.2
tst4 := new(Ab)
getType(tst)
getType(tst2)
getType(tst3)
getType(tst4)
}
Output is
Hello, playground
string
int
float64
Ab