Skip to content

Instantly share code, notes, and snippets.

@luizdepra
Created December 14, 2015 16:15
Show Gist options
  • Save luizdepra/d503601068ae5a72caae to your computer and use it in GitHub Desktop.
Save luizdepra/d503601068ae5a72caae to your computer and use it in GitHub Desktop.
How can I insert and extract data from structs dynamically in Go?
package main
import (
"fmt"
"reflect"
"strconv"
"strings"
)
type Data interface{}
type NormalData struct {
Type string `codec:"type" order:"1"`
ID int64 `codec:"id" order:"2"`
Timestamp int32 `codec:"timestamp" order:"3"`
Key int32 `codec:"key" order:"4"`
Args []interface{} `codec:"args" order:"5"`
}
func (data *NormalData) String() string {
return fmt.Sprintf("%+v", *data)
}
type ConfirmData struct {
Code int32 `codec:"code" order:"1"`
Message string `codec:"message" order:"2"`
}
func (data *ConfirmData) String() string {
return fmt.Sprintf("%+v", *data)
}
var dataRegistry = map[string]reflect.Type{
"normal": reflect.TypeOf(NormalData{}),
"confirm": reflect.TypeOf(ConfirmData{}),
}
func Extract(name string, data Data) ([]interface{}, error) {
key := strings.ToLower(name)
objType, ok := dataRegistry[key]
if !ok {
return nil, fmt.Errorf("Invalid message type '%s'", key)
}
dataType := reflect.TypeOf(data).Elem()
if dataType != objType {
return nil, fmt.Errorf("Mismatching message types")
}
elem := reflect.ValueOf(data).Elem()
result := make([]interface{}, dataType.NumField())
for i := 0; i < dataType.NumField(); i++ {
typeField := dataType.Field(i)
field := elem.Field(i)
tag, _ := strconv.Atoi(typeField.Tag.Get("order"))
index := tag - 1
result[index] = field.Interface()
}
return result, nil
}
func Build(name string, slice []interface{}) (Data, error) {
key := strings.ToLower(name)
dataType, ok := dataRegistry[key]
if !ok {
return nil, fmt.Errorf("Invalid message type '%s'", key)
}
if dataType.NumField() != len(slice) {
return nil, fmt.Errorf("Mismatching number of fields")
}
value := reflect.New(dataType)
elem := value.Elem()
for i := 0; i < dataType.NumField(); i++ {
typeField := dataType.Field(i)
field := elem.Field(i)
tag, _ := strconv.Atoi(typeField.Tag.Get("order"))
index := tag - 1
field.Set(reflect.ValueOf(slice[index]))
}
return value, nil
}
func main() {
fmt.Println("Building...")
nbs := []interface{}{"type", uint64(1234), uint64(6789), uint64(5555), []interface{}{true, uint64(0), "ok!"}}
ncs := []interface{}{int32(1234), "This is a message!"}
fmt.Printf("NormalSlice = %+v\n", nbs)
fmt.Printf("ConfirmSlice = %+v\n", ncs)
nbo, err := Build("normal", nbs)
if err != nil {
fmt.Println(err)
}
cbo, err := Build("confirm", ncs)
if err != nil {
fmt.Println(err)
}
fmt.Printf("NormalObject = %+v\n", nbo)
fmt.Printf("ConfirmObject = %+v\n", cbo)
fmt.Println("Extracting...")
neo := &NormalData{
Type: "type2",
ID: 4321,
Timestamp: 9876,
Key: 5555,
Args: []interface{}{false, 1, "nok!"},
}
ceo := &ConfirmData{
Code: 555,
Message: "This is not a message!",
}
fmt.Printf("NormalObject = %+v\n", neo)
fmt.Printf("ConfirmObject = %+v\n", ceo)
nes, err := Extract("normal", neo)
if err != nil {
fmt.Println(err)
}
ces, err := Extract("confirm", ceo)
if err != nil {
fmt.Println(err)
}
fmt.Printf("NormalSlice = %+v\n", nes)
fmt.Printf("ConfirmSlice = %+v\n", ces)
}

Hello,

I'm looking for a way to insert an extract data dynamically from structs in Go. To better undestanding, this is my scenario. I need 2 functions:

  • func Build(name string, slice []interface{}) (Data, error): where name is a object type identifier and slice is a list of values. This function must create an object that represents the type name and populate it with values form slice, and it must use the order tag to do this job.
  • func Extract(name string, data Data) ([]interface{}, error): where name is a object type identifier and data is a struct pointer. This function must create slice of interface{} with the values from the fields of the object data, again using order tag to set value order.

I need to create those two function in a generic way because I have 44 diferent structs (generated by Apache's Thrift Go implementation). I wanna avoid to code 88 functions, 2 for each object.

I'm playing with reflect package and done a lot of progress. See my main.go file. I'm having 3 problems with my code:

  • My Build function return a reflect.Value object, I wanna convert it to *ObjectType, where ObjectType is retrieved inside the function.
  • In Build function, slice will have int values as uint64 (because a default untyped conversion from a json string). I need to convert those values to the right type, as defined in the fields of ObjectType.
  • If I pass the object returned from a Build function to the Extract function I will get errors on the .Elem() from line 50. I suspect that this is caused by problem 1, but I may be wrong.

I know I can solve convertion problems with a switch clause using Kind values. But, if possible, I wanna avoid it.

So, you guys know any way to solve those problems, or any better soluction to achiev it?

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment