Use reflect to set values of struct of struct values - go

I have some code that looks to be working but does nothing in the end:
http://play.golang.org/p/TfAWWy4-R8
Have a struct that has fields of type struct. The inner struct has all string fields.
Using reflect in a loop, want to get all struct fields from outer struct. Next, populate all string values in inner struct. the example code is getting text from the tags and parsing it on "," to get strings values for inner loop.
This is the main part that should create the inner struct and add the parsed data to the string values.
t := reflect.TypeOf(Alias{})
alias = reflect.New(t)
for i := 0; i < alias.Elem().NumField(); i++ {
p := alias.Elem().Field(i)
p.SetString(params[i])
}
It looks like it is working when you look at the output from example, but after printing a value from outer struct it seems to be empty:
fmt.Println("Final01 = ", Attr.Final01.Command) // prints empty string
So not sure how to get values into the inner struct Alias{}
Thanks for any help.

Here's the working program. Explanation below.
package main
import "fmt"
import "strings"
import "reflect"
type Alias struct {
Name string
DevicePath string
GuiPath string
Setpoint string
Command string
Status string
FunctionCmds string
}
type Manifold struct {
Final01 Alias "Final01,/Gaspanel/Shared/Final01,,,wOpen,rIsOpen,"
Dump01 Alias "Dump01,/Gaspanel/Shared/Dump01,,,wOpen,rIsOpen,"
N2Vent01 Alias "N2Vent01,/Gaspanel/Shared/N2Vent01,,,wOpen,rIsOpen,"
N2Vent201 Alias "N2Vent201,/Gaspanel/Shared/N2Vent201,,,wOpen,rIsOpen,"
PurgeValve Alias "PurgeValve,/Gaspanel/Shared/Purge01,,,wOpen,rIsOpen,"
}
func MapTagedAliasToChamber(chamber string, struc interface{}) []string {
attributeStruct := reflect.ValueOf(struc).Elem()
typeAttributeStruct := attributeStruct.Type()
attributes := make([]string, attributeStruct.NumField(), attributeStruct.NumField())
for i := 0; i < attributeStruct.NumField(); i++ {
alias := attributeStruct.Field(i)
tag := string(typeAttributeStruct.Field(i).Tag)
name := typeAttributeStruct.Field(i).Name
params := strings.Split(tag, ",")
alias = reflect.New(reflect.TypeOf(Alias{})).Elem()
for i := 0; i < alias.NumField(); i++ {
alias.Field(i).SetString(params[i])
}
attributeStruct.Field(i).Set(alias)
fmt.Printf("%d: %s %s = %v\n", i, name, alias.Type(), alias.Interface())
}
return attributes
}
func main() {
Attr := Manifold{}
MapTagedAliasToChamber("A", &Attr)
fmt.Println("Final01 = ", Attr.Final01.Command)
}
The problem was on line 38 of your original program, where you created a new reflect.Value named alias representing a value of type *Alias, then filled it with your information but never wrote it back into your Manifold struct.
Additionally I suggest that you stick to the standard struct-tag format which can be parsed and used more easily through (reflect.StructTag).Get(key string). And don't use strings where you don't need them e.g. rIsOpen sounds like a boolean value to me.

Related

How to convert a struct into a flat array of paths?

If for example I have a struct such as:
type Example struct {
Foo string
Bar string
Baz struct{
A int
B string
}
Qux []string
}
What would be the best approach to convert it into a flat slice of strings representing the dot path to each struct field?
I want an output that looks like the following slice:
["Example.Foo", "Example.Bar", "Example.Baz.A", "Example.Baz.B", "Example.Qux.0", "Example.Qux.1"]
The exact structs will be known at compile time. Also, the conversion from struct to a flat list is in a hot path so performance will be an important consideration.
Any hints would be appreciated!
You have to code it yourself, with reflection.
This is a demonstrative function that prints the output you provided:
package main
import (
"fmt"
"reflect"
"strconv"
)
type Example struct {
Foo string
Bar string
Baz struct{
A int
B string
}
Qux []string
}
func main() {
example := Example{Qux: []string{"a", "b"}}
t := reflect.ValueOf(example)
prefix := t.Type().Name()
fmt.Println(ToPathSlice(t, prefix, make([]string, 0)))
}
func ToPathSlice(t reflect.Value, name string, dst []string) []string {
switch t.Kind() {
case reflect.Ptr, reflect.Interface:
return ToPathSlice(t.Elem(), name, dst)
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
fname := t.Type().Field(i).Name
dst = ToPathSlice(t.Field(i), name+"."+fname, dst)
}
case reflect.Slice, reflect.Array:
for i := 0; i < t.Len(); i++ {
dst = ToPathSlice(t.Index(i), name+"."+strconv.Itoa(i), dst)
}
default:
return append(dst, name)
}
return dst
}
Will print:
[Example.Foo Example.Bar Example.Baz.A Example.Baz.B Example.Qux.0 Example.Qux.1]
Note that:
reflection comes at a performance penalty; if you are concerned about this, you should profile the relevant code path to see if it's a deal breaker
the above code is contrived, for example it doesn't handle maps, it doesn't handle nil, etc.; you can expand it yourself
in your desired output, the indices of slice/array fields is printed. Slices don't have inherent length as arrays. In order to know the length of slices, you have to work with reflect.Value. This IMO makes the code more awkward. If you can accept not printing indices for slices, then you can work with reflect.Type.
Playground: https://play.golang.org/p/isNFSfFiXOP

Loop through the Nth fields one time of struct n a Go

Iterate through the fields of a struct in Go
I have read above thread and now I'm trying to extend it by processing multiple items at a time
package main
import (
"fmt"
"reflect"
)
type BaseStats struct {
value1 int
value2 byte
value3 int
value4 byte
}
func StatsCreate(stats BaseStats) {
v := reflect.ValueOf(stats)
val := make([]interface{}, v.NumField())
for i := 0; i< v.NumField(); i+=2 {
val[i+0] = v.Field(i+0).Interface().(int)
val[i+1] = v.Field(i+1).Interface().(byte)
fmt.Printf("%v %v", val[i+0], val[i+1])
}
}
func main() {
StatsCreate(BaseStats{20, '%', 400, '$'})
}
Error I had before: panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
Go requires you to make the first letter of any variable or function that you wish to use outside of the package (aka EXPORTED). Because you're trying to pass variables to another package, in this case the reflect package, but they're not exportable. It's very common that all struct field names are capitalized as best practice.
The capitalized->exported paradigm is one of the few things I don't like about the language, but that's the way it is.

How can you dynamically resolve or introspect variables

Is there a way to resolve or introspect a variable identified from a string of its name?
For instance, if I have a string value strVal := "s" that corresponds to the name of a variable such as var s string which is in the same function
's scope, can I dynamically look up the value of the s variable?
I know if these were fields of a struct, I could use reflection, but for my use case, I am dealing with locally scoped variables that are not part of a struct or map.
It seems like you cloud extract this information from a struct.
package main
import (
"fmt"
"reflect"
)
type Article struct {
Id int
Title string
Price float32
Authors []string
}
func main() {
article := Article{}
e := reflect.ValueOf(&article).Elem()
for i := 0; i < e.NumField(); i++ {
name := e.Type().Field(i).Name
varType := e.Type().Field(i).Type
value := e.Field(i).Interface()
fmt.Printf("%v %v %v\n", name, varType, value)
}
}
Example:
https://play.golang.org/p/vWkRzpvWKYn
If you want to analyze while runtime a variable then you could use a debugger like delve
Ref: https://github.com/go-delve/delve
[I]f I have a string value strVal := "s" that corresponds to the name of a var s string in the same scope, can I dynamically look up the value of s?
No. A dead simple No.

How to dynamically create a struct with one less property?

Is there a way to copy a generic struct (i.e. a struct whose property names are unknown) and skip a single, known property?
Here is what I know:
The parameter to my function--I will call the parameter myData-- is of type interface{}.
myData is a struct.
myData has a known property path.
myData has anywhere from 0 to 6 or so other properties, none of which are known a priori.
Once I remove that path property, then the “leftover” is one of say 30 possible struct types.
So I want to strip path out of myData (or more accurately make a copy that omits path) so that various bits of generated code that try to coerce the struct to one of its possible types will be able to succeed.
I have found examples of copying a struct by reflection, but they typically create an empty struct of the same underlying type, then fill it in. So is it even possible to delete a property as I have outlined...?
You can use reflect.StructOf to dynamically create structs from a list of fields.
package main
import (
"fmt"
"reflect"
)
type A struct {
Foo string
Bar int
Baz bool // to be skipped
}
type B struct {
Foo string
Bar int
}
func main() {
av := reflect.ValueOf(A{"hello", 123, true})
fields := make([]reflect.StructField, 0)
values := make([]reflect.Value, 0)
for i := 0; i < av.NumField(); i++ {
f := av.Type().Field(i)
if f.Name != "Baz" {
fields = append(fields, f)
values = append(values, av.Field(i))
}
}
typ := reflect.StructOf(fields)
val := reflect.New(typ).Elem()
for i := 0; i < len(fields); i++ {
val.Field(i).Set(values[i])
}
btyp := reflect.TypeOf(B{})
bval := val.Convert(btyp)
b, ok := bval.Interface().(B)
fmt.Println(b, ok)
}

Golang get string representation of specific struct field name

I really want a way to print the string representation of a field name in go. It has several use cases, but here is an example:
lets say I have a struct
type Test struct {
Field string `bson:"Field" json:"field"`
OtherField int `bson:"OtherField" json:"otherField"`
}
and, for example, I want to do a mongo find:
collection.Find(bson.M{"OtherField": someValue})
I don't like that I have to put the string "OtherField" in there. It seems brittle and easy to either misstype or have the struct change and then my query fails without me knowing it.
Is there any way to get the string "OtherField" without having to either declare a const or something like that? I know I can use reflection to a get a list of field names from a struct, but I'd really like to do something along the lines of
fieldName := nameOf(Test{}.OtherField)
collection.Find(bson.M{fieldName: someValue})
is there any way to do this in Go?? C# 6 has the built in nameof, but digging through reflection I can't find any way to do this in Go.
I don't really think there is. You may be able to load a set of types via reflection and generate a set of constants for the field names. So:
type Test struct {
Field string `bson:"Field" json:"field"`
OtherField int `bson:"OtherField" json:"otherField"`
}
Could generate something like:
var TestFields = struct{
Field string
OtherField string
}{"Field","OtherField"}
and you could use TestFields.Field as a constant.
Unfortunately, I don't know of any existing tool that does anything like that. Would be fairly simple to do, and wire up to go generate though.
EDIT:
How I'd generate it:
Make a package that accepts an array of reflect.Type or interface{} and spits out a code file.
Make a generate.go somewhere in my repo with main function:
func main(){
var text = mygenerator.Gen(Test{}, OtherStruct{}, ...)
// write text to constants.go or something
}
Add //go:generate go run scripts/generate.go to my main app and run go generate
Here is a function that will return a []string with the struct field names. I think it comes in the order they are defined.
WARNING: Reordering the fields in the struct definition will change the order in which they appear
https://play.golang.org/p/dNATzNn47S
package main
import (
"fmt"
"strings"
"regexp"
)
type Test struct {
Field string `bson:"Field" json:"field"`
OtherField int `bson:"OtherField" json:"otherField"`
}
func main() {
fields, err := GetFieldNames(Test{})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(fields)
}
func GetFieldNames(i interface{}) ([]string, error) {
// regular expression to find the unquoted json
reg := regexp.MustCompile(`(\s*?{\s*?|\s*?,\s*?)(['"])?(?P<Field>[a-zA-Z0-9]+)(['"])?:`)
// print struct in almost json form (fields unquoted)
raw := fmt.Sprintf("%#v", i)
// remove the struct name so string begins with "{"
fjs := raw[strings.Index(raw,"{"):]
// find and grab submatch 3
matches := reg.FindAllStringSubmatch(fjs,-1)
// collect
fields := []string{}
for _, v := range matches {
if len(v) >= 3 && v[3] != "" {
fields = append(fields, v[3])
}
}
return fields, nil
}

Resources