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...
Related
I can't explain why the following is working.
package main
import (
"fmt"
"reflect"
"strings"
)
type MyInterface interface {
someFunc()
}
type Dock struct {
}
func (d *Dock) someFunc() {
}
type Group struct {
Docks []Dock `better:"sometag"`
}
func foo(model interface{}) {
v1 := reflect.Indirect(reflect.ValueOf(model))
for i := 0; i < v1.NumField(); i++ {
tag := v1.Type().Field(i).Tag.Get("better")
if strings.HasPrefix(tag, "sometag") {
inter := v1.Field(i).Interface()
typ := reflect.TypeOf(inter).Elem()
fmt.Println("Type:", typ.String())
// Want to instantiate type like &Dock{} then assign it to some interface,
// but using reflect
n := reflect.New(typ)
_, ok := n.Interface().(MyInterface)
fmt.Println("Why is it OK?", ok)
}
}
}
func main() {
g := &Group{}
foo(g)
/*var v1, v2 interface{}
d1 := &Dock{}
v1 = d1
_, ok1 := v1.(MyInterface)
d2 := Dock{}
v2 = d2
_, ok2 := v2.(MyInterface)
fmt.Println(ok1, ok2)*/
}
It prints
Type: main.Dock
OK? true
If it's a Dock type, then it's not a pointer to Dock. Why does it conforms to MyInterface?
https://play.golang.org/p/Z9mR8amYOM7
Where as the d2 example in the comment does not.
In go doc for reflect.New
New returns a Value representing a pointer to a new zero value for the
specified type. That is, the returned Value's Type is PtrTo(typ).
n := reflect.New(typ)
fmt.Println("Type:", n.String())
It will print Type: <*main.Dock Value> means n is a pointer of Dock.You miss the part using reflect.New return the pointer.
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.
I'm parsing a JSON object which contains an array of strings :
var ii interface{}
json := "{\"aString\": [\"aaa_111\", \"bbb_222\"], \"whatever\":\"ccc\"}"
err := json.Unmarshal([]byte(json), &ii)
if err != nil {
log.Fatal(err)
}
data := ii.(map[string]interface{})
fmt.Println(data["aString"]) // outputs: ["aaa_111" "bbb_222"]
I tried to convert data["aString"] to []string to be able to loop over it, but it fails :
test := []string(data["aString"]).([]string)
fmt.Println(test) // panic -> interface conversion:
// interface is string, not []string
How can I convert data["aString"] ?
edit:
I didn't express myself properly. If I print data, I have such map :
map[aString:["BBB-222","AAA-111"] whatever:ccc]
I want to loop over aString (to manipule each array entry). But I can't find how, because aString is type interface {} :
for i, v := range aString { // <-- fails
// ...
fmt.Println(i, v)
}
That's why I want to convert aString. I don't want to convert a string which looks like an array to an array.
I recommend you move away from this implementation in general. Your json may vary but you can easily use objects and avoid all this type unsafe nonsense.
Anyway, that conversion doesn't work because the types inside the slice are not string, they're also interface{}. You have to iterate the collection then do a type assertion on each item like so:
aInterface := data["aString"].([]interface{})
aString := make([]string, len(aInterface))
for i, v := range aInterface {
aString[i] = v.(string)
}
Is it what you need?
package main
import (
"fmt"
"encoding/json"
)
func main() {
js := "{\"aString\": [\"aaa_111\", \"bbb_222\"], \"whatever\":\"ccc\"}"
a := make(map[string]interface{})
json.Unmarshal([]byte(js), &a)
for _, v := range a["aString"].([]interface{}) {
str := v.(string)
fmt.Println(str)
}
}
Check on Go Playground
For another approach, you can use a struct instead:
package main
import (
"encoding/json"
"fmt"
)
func main() {
s := []byte(`{"aString": ["aaa_111", "bbb_222"], "whatever":"ccc"}`)
var t struct {
Astring []string
Whatever string
}
json.Unmarshal(s, &t)
fmt.Printf("%+v\n", t) // {Astring:[aaa_111 bbb_222] Whatever:ccc}
}
package main
import (
"fmt"
)
type alias int
type aliases []*alias
func main() {
a1 := alias(1)
t := aliases{&a1}
fmt.Println([]*int([]*alias(t)))
}
The type type aliases []*alias is essentially []*int
I want to be able to type convert aliases back to []*int
You can with unsafe.Pointer, a little bit unsafe so not recommended
PointerToSliceOfPointersToInt := (*([]*int))(unsafe.Pointer(&t))
try it works https://play.golang.org/p/6AWd1W_it3
Try this, you could do that by doing right casting.
type alias int
type aliases []*alias
func main() {
a1 := alias(1)
t := aliases{&a1}
orig := int(*([]*alias(t)[0]))
fmt.Println(orig)
}
Example on http://play.golang.org/p/1WosCIUZSa
If you want to get all values (not just the first index) you have to loop and cast each element.
func main() {
a1 := alias(1)
t := aliases{&a1}
orig := []*int{}
for _, each := range t {
temp := int(*each)
orig = append(orig, &temp)
}
fmt.Printf("%#v\n", orig) // []*int{(*int)(0x10434114)}
}
Example: http://play.golang.org/p/Sx4JK3kA45
Using Go’s ast package, I am looping over a struct’s field list like so:
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}
// typ is a *ast.StructType representing the above
for _, fld := range typ.Fields.List {
// get fld.Type as string
}
…and would like to get a simple string representation of fld.Type, as it appears in the source code, e.g. []int or map[byte]float64.
The ast package field type Type property is an Expr, so I’ve found myself getting off into the weeds using type switches and handling every type specifically – when my only goal is to get out the plain string to the right of each field name, which seems like it should be simpler.
Is there a simple way?
There are two things you could be getting at here, one is the type of an expression as would ultimately be resolved during compilation and the other is the code which would determine that type.
Digging through the docs, I don't believe the first is at all available. You can get at the later, however, by using End() and Pos() on Node.
Quick example program:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
src := `
package foo
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
// hard coding looking these up
typeDecl := f.Decls[0].(*ast.GenDecl)
structDecl := typeDecl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType)
fields := structDecl.Fields.List
for _, field := range fields {
typeExpr := field.Type
start := typeExpr.Pos() - 1
end := typeExpr.End() - 1
// grab it in source
typeInSource := src[start:end]
fmt.Println(typeInSource)
}
}
This prints:
string
[]int
map[byte]float64
I through this together in the golang playground, if you want to mess with it.
You can use go/types ExprString
This works with complicated types like []string, []map[string]string, etc.
import (
...
"go/types"
...
)
...
// typ is a *ast.StructType representing the above
for _, fld := range typ.Fields.List {
...
typeExpr := fld.Type
typeString := types.ExprString(typeExpr)
...
}
https://golang.org/src/go/types/exprstring.go
This is exactly what Fprint in the go/printer package is for. It takes any AST node as an argument and writes its string representation to a io.Writer.
You can use it in your example as follows:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"log"
)
func main() {
src := `
package foo
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
typeDecl := f.Decls[0].(*ast.GenDecl)
structDecl := typeDecl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType)
for i, fld := range structDecl.Fields.List {
// get fld.Type as string
var typeNameBuf bytes.Buffer
err := printer.Fprint(&typeNameBuf, fset, fld.Type)
if err != nil {
log.Fatalf("failed printing %s", err)
}
fmt.Printf("field %d has type %q\n", i, typeNameBuf.String())
}
}
Output:
field 0 has type "string"
field 1 has type "[]int"
field 2 has type "map[byte]float64"
Try it in playground: https://play.golang.org/p/cyrCLt_JEzQ
I found a way to do this without using the original source code as a reference for simple members (not slices, arrays or structs):
for _, field := range fields {
switch field.Type.(type) {
case *ast.Ident:
stype := field.Type.(*ast.Ident).Name // The type as a string
tag = ""
if field.Tag != nil {
tag = field.Tag.Value //the tag as a string
}
name := field.Names[0].Name //name as a string
...
For the non-simple members you just need another case statement (IE: case *ast.ArrayType:).