field: add Set() method for setting fields

This commit is contained in:
Fatih Arslan 2014-08-15 12:26:36 +03:00
parent 747a05cbb4
commit add65496af
3 changed files with 100 additions and 2 deletions

View File

@ -95,6 +95,9 @@ name := s.Field("Name")
// Get the underlying value, value => "gopher"
value := name.Value().(string)
// Set the field's value
name.Set("another gopher")
// Check if the field is exported or not
if name.IsExported() {
fmt.Println("Name field is exported")

View File

@ -1,6 +1,15 @@
package structs
import "reflect"
import (
"errors"
"fmt"
"reflect"
)
var (
errNotExported = errors.New("field is not exported")
errNotSettable = errors.New("field is not settable")
)
// Field represents a single struct field that encapsulates high level
// functions around the field.
@ -45,6 +54,32 @@ func (f *Field) Name() string {
return f.field.Name
}
// Set sets the field to given value v. It retuns an error if the field is not
// settable (not addresable or not exported) or if the given value's type
// doesn't match the fields type.
func (f *Field) Set(val interface{}) error {
// needed to make the field settable
v := reflect.Indirect(f.value)
if !f.IsExported() {
return errNotExported
}
// do we get here? not sure...
if !v.CanSet() {
return errNotSettable
}
given := reflect.ValueOf(val)
if v.Kind() != given.Kind() {
return fmt.Errorf("wrong kind: %s want: %s", given.Kind(), v.Kind())
}
v.Set(given)
return nil
}
// Field returns the field from a nested struct. It panics if the nested struct
// is not exported or if the field was not found.
func (f *Field) Field(name string) *Field {

View File

@ -9,7 +9,9 @@ type Foo struct {
C bool `json:"c"`
d string // not exported
x string `xml:"x"` // not exported, with tag
*Bar // embedded
Y []string
Z map[string]interface{}
*Bar // embedded
}
type Bar struct {
@ -30,12 +32,70 @@ func newStruct() *Struct {
A: "gopher",
C: true,
d: "small",
Y: []string{"example"},
Z: nil,
}
f.Bar = b
return New(f)
}
func TestField_Set(t *testing.T) {
s := newStruct()
f := s.Field("A")
err := f.Set("fatih")
if err != nil {
t.Error(err)
}
if f.Value().(string) != "fatih" {
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih")
}
f = s.Field("Y")
err = f.Set([]string{"override", "with", "this"})
if err != nil {
t.Error(err)
}
sliceLen := len(f.Value().([]string))
if sliceLen != 3 {
t.Errorf("Setted values slice length is wrong: %d, want: %d", sliceLen, 3)
}
f = s.Field("C")
err = f.Set(false)
if err != nil {
t.Error(err)
}
if f.Value().(bool) {
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(bool), false)
}
// let's pass a different type
f = s.Field("A")
err = f.Set(123) // Field A is of type string, but we are going to pass an integer
if err == nil {
t.Error("Setting a field's value with a different type than the field's type should return an error")
}
// old value should be still there :)
if f.Value().(string) != "fatih" {
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih")
}
// let's access an unexported field, which should give an error
f = s.Field("d")
err = f.Set("large")
if err != errNotExported {
t.Error(err)
}
// TODO: let's access a non addresable field, which should give an error
}
func TestField(t *testing.T) {
s := newStruct()