Skip to content

Instantly share code, notes, and snippets.

@farnoy
Created March 16, 2012 10:58
Show Gist options
  • Select an option

  • Save farnoy/2049589 to your computer and use it in GitHub Desktop.

Select an option

Save farnoy/2049589 to your computer and use it in GitHub Desktop.
Reflection assignment for struct fields in Go
package fields
import (
"fmt"
"reflect"
)
// Provides a shortcut and prettier syntax for map definitions.
type Assigns map[string]reflect.Value
// Returns an array of field names for given interface
// that must be a struct.
func CollectFieldNames(object interface{}) (names []string) {
typ := reflect.TypeOf(object)
if typ.Kind() == reflect.Ptr { typ = typ.Elem() }
if typ.Kind() != reflect.Struct {
fmt.Printf("%v type can't have attributes inspected", typ.Kind())
return
}
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
if !f.Anonymous {
names = append(names, f.Name)
}
}
return
}
// Assign values to object's fields by matching
// fields by names given in data and taken from object.
//
// The object must be a reference (addressable).
//
// If the object given is not a struct, warns and quits.
//
// When the above condition is satisfied, then for each pair in the given map:
//
// If there is no field with wanted name
// <=> Nothing happens
// If there is a field with wanted name:
// * And the type of the value to assign is different from the type of matched field
// <=> Nothing happens
// * And the type of the value to assign is the same as the type of matched field
// <=> Value is assigned to that field
//
// Returns an error when unmarshalling can't be done for object
func Unmarshal(data Assigns, object interface{}) error {
v := reflect.ValueOf(object)
if v.Kind() == reflect.Ptr {
v = v.Elem()
} else {
return fmt.Errorf("object for unmarshalling has to be addressable")
}
if v.Kind() != reflect.Struct {
return fmt.Errorf("type can't have attributes inspected: %v", v.Kind())
}
for name, val := range data {
if field := v.FieldByName(name); field.IsValid() && val.Kind() == field.Kind() {
field.Set(val)
}
}
return nil
}
package fields
import (
"testing"
"reflect"
)
type Container struct {
Key string
Value int
}
func ExampleUnmarshal() {
c := Container{}
Unmarshal(Assigns{"Key": reflect.ValueOf("test")}, &c) // remember to use a reference here
print(c.Key) // "test"
}
func TestUnmarshalWithGoodSettings(t *testing.T) {
c := Container{}
Unmarshal(Assigns{"Key": reflect.ValueOf("test")}, &c)
if c.Key != "test" { t.Error("Expected Unmarshal to change field accordingly") }
}
func TestUnmarshalWithWrongSettings(t *testing.T) {
c := Container{}
Unmarshal(Assigns{"Key": reflect.ValueOf(5)}, &c)
if c.Key != "" { t.Error("Expected Unmarshal to not change field when types do not match") }
}
func TestUnmarshalWithWrongFields(t *testing.T) {
c := Container{}
Unmarshal(Assigns{"DoesNotExist": reflect.ValueOf(nil)}, &c)
if c.Key != "" || c.Value != 0 { t.Error("Expected Unmarshal to not change anything when no field matches") }
}
func TestUnmarshalRaisingExceptionWithUnaddressable(t *testing.T) {
c := Container{}
if err := Unmarshal(Assigns{"DoesNotExist": reflect.ValueOf(nil)}, c); err.Error() != "object for unmarshalling has to be addressable" {
t.Error("Expected Unmarshal to catch unaddressable objects")
}
}
package main
import (
"fmt"
"goref/fields"
"reflect"
)
type Container struct {
Name string
Value int
}
func main() {
cont := Container{}
fmt.Println("Container before", cont)
fmt.Println("\nFields:")
for _, name := range fields.CollectFieldNames(cont) {
fmt.Println("*", name)
}
m := fields.Assigns{
"Name": reflect.ValueOf("Kuba"),
"Value": reflect.ValueOf(16),
"UNREAL": reflect.ValueOf(1e2),
}
fields.Unmarshal(m, &cont)
fmt.Println("\nContainer after", cont)
}
Container before { 0}
Fields:
* Name
* Value
Container after {Kuba 16}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment