diff --git a/README.md b/README.md index 80d9658..bd424f9 100644 --- a/README.md +++ b/README.md @@ -18,49 +18,63 @@ Lets define and declare a struct ```go type Server struct { - Name string - ID int32 - Enabled bool + Name string `json:"name,omitempty"` + ID int + Enabled bool + users []string // not exported + http.Server // embedded } -s := &Server{ +server := &Server{ Name: "gopher", ID: 123456, Enabled: true, } ``` +### Struct methods + +Let's create a new `Struct` type. + ```go +// Create a new struct type: +s := structure.New(server) + // Convert a struct to a map[string]interface{} // => {"Name":"gopher", "ID":123456, "Enabled":true} -m := structure.Map(s) +m := s.Map() // Convert the values of a struct to a []interface{} -// => [123456, "gopher", true] -v := structure.Values(s) - -// Convert the fields of a struct to a []string. -// => ["Name", "ID", "Enabled"] -f := structure.Fields(s) - -// Return the struct name -// => "Server" -n := structure.Name(s) - -// Check if field name exists -// => true -h := structure.Has(s, "Enabled") +// => ["gopher", 123456, true] +v := s.Values() // Check if any field of a struct is initialized or not. -if structure.HasZero(s) { +if s.HasZero() { fmt.Println("s has a zero value field") } // Check if all fields of a struct is initialized or not. -if structure.IsZero(s) { +if s.IsZero() { fmt.Println("all fields of s is zero value") } +// Return the struct name +// => "Server" +n := s.Name() +``` + +Most of the struct methods are available as global functions without the need +for a `New()` constructor: + +```go +m := structure.Map(s) +v := structure.Values(s) +f := structure.Fields(s) +n := structure.Name(s) + +hasZero := structure.HasZero(s) +isZero := structure.IsZero(s) + // Check if it's a struct or a pointer to struct if structure.IsStruct(s) { fmt.Println("s is a struct") @@ -68,6 +82,66 @@ if structure.IsStruct(s) { ``` +### Field methods + +We can easily examine a single Field for more detail. Below you can see how we +get and interact with various field methods: + + +```go +s := structure.New(server) + +// Get the Field struct for the "Name" field +name := s.Field("Name") + +// Get the underlying value, value => "gopher" +value := name.Value().(string) + +// Check if the field is exported or not +if name.IsExported() { + fmt.Println("Name field is exported") +} + +// Check if the value is a zero value, such as "" for string, 0 for int +if !name.IsZero() { + fmt.Println("Name is initialized") +} + +// Check if the field is an anonymous (embedded) field +if !name.IsEmbedded() { + fmt.Println("Name is not an embedded field") +} + +// Get the Field's tag value for tag name "json", tag value => "name,omitempty" +tagValue := name.Tag("json") +``` + +Nested structs are supported too: + +```go +addrField := s.Field("Server").Field("Addr") + +// Get the value for addr +a := addrField.Value().(string) +``` + +We can also get a slice of Fields from the Struct type to iterate over all +fields. This is handy if you whish to examine all fields: + +```go +// Convert the fields of a struct to a []*Field +fields := s.Fields() + +for _, f := range fields { + fmt.Printf("field name: %+v\n", f.Name()) + + if f.IsExported() { + fmt.Printf("value : %+v\n", f.Value()) + fmt.Printf("is zero : %+v\n", f.Value()) + } +} +``` + ## Credits * [Fatih Arslan](https://github.com/fatih) diff --git a/field.go b/field.go index bf2e761..358a071 100644 --- a/field.go +++ b/field.go @@ -56,8 +56,9 @@ func (f *Field) Field(name string) *Field { return field } -// Field returns the field from a nested struct. It panics if the nested struct -// is not exported or if the field was not found. +// Field returns the field from a nested struct. The boolean returns true if +// the field was not found. It panics if the nested struct is not exported or +// if the field was not found. func (f *Field) FieldOk(name string) (*Field, bool) { v := strctVal(f.value.Interface()) t := v.Type() diff --git a/structure.go b/structure.go index 844d1fe..617a2df 100644 --- a/structure.go +++ b/structure.go @@ -120,43 +120,35 @@ func (s *Struct) Values() []interface{} { return t } -// Fields returns a slice of field names. A struct tag with the content of "-" +// Fields returns a slice of Fields. A struct tag with the content of "-" // ignores the checking of that particular field. Example: // // // Field is ignored by this package. // Field bool `structure:"-"` // -// A value with the option of "omitnested" stops iterating further if the type -// is a struct. Example: -// -// // Field is not processed further by this package. -// Field time.Time `structure:"myName,omitnested"` -// Field *http.Request `structure:",omitnested"` -// -// Note that only exported fields of a struct can be accessed, non exported -// fields will be neglected. It panics if s's kind is not struct. -func (s *Struct) Fields() []string { - fields := s.structFields() +// It panics if s's kind is not struct. +func (s *Struct) Fields() []*Field { + t := s.value.Type() - var keys []string + var fields []*Field - for _, field := range fields { - val := s.value.FieldByName(field.Name) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) - _, tagOpts := parseTag(field.Tag.Get(DefaultTagName)) - - if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { - // look out for embedded structs, and convert them to a - // []string to be added to the final values slice - for _, embeddedVal := range Fields(val.Interface()) { - keys = append(keys, embeddedVal) - } + if tag := field.Tag.Get(DefaultTagName); tag == "-" { + continue } - keys = append(keys, field.Name) + f := &Field{ + field: field, + value: s.value.FieldByName(field.Name), + } + + fields = append(fields, f) + } - return keys + return fields } // Field returns a new Field struct that provides several high level functions @@ -340,9 +332,9 @@ func Values(s interface{}) []interface{} { return New(s).Values() } -// Fields returns a slice of field names. For more info refer to Struct types +// Fields returns a slice of *Field. For more info refer to Struct types // Fields() method. It panics if s's kind is not struct. -func Fields(s interface{}) []string { +func Fields(s interface{}) []*Field { return New(s).Fields() } diff --git a/structure_example_test.go b/structure_example_test.go index 2f59374..02eacb7 100644 --- a/structure_example_test.go +++ b/structure_example_test.go @@ -148,11 +148,16 @@ func ExampleFields() { Number: 1234567, } - m := Fields(s) + fields := Fields(s) + + for i, field := range fields { + fmt.Printf("[%d] %+v\n", i, field.Name()) + } - fmt.Printf("Fields: %+v\n", m) // Output: - // Fields: [Name LastAccessed Number] + // [0] Name + // [1] LastAccessed + // [2] Number } func ExampleFields_nested() { @@ -162,7 +167,7 @@ func ExampleFields_nested() { } type Access struct { - Person Person `structure:",omitnested"` + Person Person HasPermission bool LastAccessed time.Time } @@ -173,13 +178,17 @@ func ExampleFields_nested() { HasPermission: true, } - // Let's get all fields from the struct s. Note that we don't include the - // fields from the Person field anymore due to "omitnested" tag option. - m := Fields(s) + // Let's get all fields from the struct s. + fields := Fields(s) + + for _, field := range fields { + if field.Name() == "Person" { + fmt.Printf("Access.Person.Name: %+v\n", field.Field("Name").Value()) + } + } - fmt.Printf("Fields: %+v\n", m) // Output: - // Fields: [Person HasPermission LastAccessed] + // Access.Person.Name: fatih } func ExampleIsZero() { diff --git a/structure_test.go b/structure_test.go index da800cd..342cf71 100644 --- a/structure_test.go +++ b/structure_test.go @@ -399,7 +399,7 @@ func TestFields(t *testing.T) { inSlice := func(val string) bool { for _, v := range s { - if reflect.DeepEqual(v, val) { + if reflect.DeepEqual(v.Name(), val) { return true } } @@ -416,27 +416,27 @@ func TestFields(t *testing.T) { func TestFields_OmitNested(t *testing.T) { type A struct { Name string - Value string - Number int Enabled bool } a := A{Name: "example"} type B struct { - A A `structure:",omitnested"` - C int + A A + C int + Value string `structure:"-"` + Number int } b := &B{A: a, C: 123} s := Fields(b) - if len(s) != 2 { + if len(s) != 3 { t.Errorf("Fields should omit nested struct. Expecting 2 got: %d", len(s)) } inSlice := func(val interface{}) bool { for _, v := range s { - if reflect.DeepEqual(v, val) { + if reflect.DeepEqual(v.Name(), val) { return true } } @@ -450,36 +450,6 @@ func TestFields_OmitNested(t *testing.T) { } } -func TestFields_Nested(t *testing.T) { - type A struct { - Name string - } - a := A{Name: "example"} - - type B struct { - A A - C int - } - b := &B{A: a, C: 123} - - s := Fields(b) - - inSlice := func(val interface{}) bool { - for _, v := range s { - if reflect.DeepEqual(v, val) { - return true - } - } - return false - } - - for _, val := range []interface{}{"Name", "A", "C"} { - if !inSlice(val) { - t.Errorf("Fields should have the value %v", val) - } - } -} - func TestFields_Anonymous(t *testing.T) { type A struct { Name string @@ -497,14 +467,14 @@ func TestFields_Anonymous(t *testing.T) { inSlice := func(val interface{}) bool { for _, v := range s { - if reflect.DeepEqual(v, val) { + if reflect.DeepEqual(v.Name(), val) { return true } } return false } - for _, val := range []interface{}{"Name", "A", "C"} { + for _, val := range []interface{}{"A", "C"} { if !inSlice(val) { t.Errorf("Fields should have the value %v", val) }