From fe137c012f708e0658ebd011a60bf5f8836490b9 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Sun, 10 Aug 2014 15:47:04 +0300 Subject: [PATCH 01/10] structure: add constructor to provide a more advanced functions around structs --- field.go | 20 ++++++++++++ structs.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 field.go create mode 100644 structs.go diff --git a/field.go b/field.go new file mode 100644 index 0000000..510ab71 --- /dev/null +++ b/field.go @@ -0,0 +1,20 @@ +package structure + +import "reflect" + +// Field represents a single struct field that encapsulates many high level +// function around a singel struct field +type Field struct { + value reflect.Value + field reflect.StructField +} + +// Tag returns the value associated with key in the tag string. If there is no +// such key in the tag, Tag returns the empty string +func (f *Field) Tag(key string) string { + return f.field.Tag.Get(key) +} + +func (f *Field) Value() interface{} { + return f.value.Interface() +} diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..87c1465 --- /dev/null +++ b/structs.go @@ -0,0 +1,89 @@ +package structure + +// Struct encapsulates a struct type to provide several high level functions +// around the structure. +type Struct struct { + raw interface{} +} + +// New returns a new *Struct with the struct s. +func New(s interface{}) *Struct { + return &Struct{ + raw: s, + } +} + +// Map converts the given struct to a map[string]interface{}. For more info +// refer to Map() function. +func (s *Struct) Map() map[string]interface{} { + return Map(s.raw) +} + +// Values converts the given struct to a []interface{}. For more info refer to +// Values() function. +func (s *Struct) Values() []interface{} { + return Values(s.raw) +} + +// Fields returns a slice of field names For more info refer to Fields() +// function. +func (s *Struct) Fields() []string { + return Fields(s.raw) +} + +// Field returns a new Field struct that provides several high level functions +// around a single struct field entitiy. It panics if the field is not found or +// is unexported. +func (s *Struct) Field(name string) *Field { + f, ok := s.FieldOk(name) + if !ok { + panic("field not found") + } + + return f +} + +// Field returns a new Field struct that provides several high level functions +// around a single struct field entitiy and a boolean indicating if the field +// was found. It panics if the or is unexported. +func (s *Struct) FieldOk(name string) (*Field, bool) { + v := strctVal(s.raw) + t := v.Type() + + field, ok := t.FieldByName(name) + if !ok { + return nil, false + } + + if field.PkgPath != "" { + panic("unexported field access is not allowed") + } + + return &Field{ + field: field, + value: v.FieldByName(name), + }, true +} + +// IsZero returns true if all fields is equal to a zero value. For more info +// refer to IsZero() function. +func (s *Struct) IsZero() bool { + return IsZero(s.raw) +} + +// HasZero returns true if any field is equal to a zero value. For more info +// refer to HasZero() function. +func (s *Struct) HasZero() bool { + return HasZero(s.raw) +} + +// Name returns the structs's type name within its package. For more info refer +// to Name() function. +func (s *Struct) Name() string { + return Name(s) +} + +// IsStruct returns true if its a struct or a pointer to struct. +func (s *Struct) IsStruct() bool { + return IsStruct(s.raw) +} From 53f565ab3ea55b25dfbdb5335fac84eb23c08e80 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Sun, 10 Aug 2014 15:53:25 +0300 Subject: [PATCH 02/10] field: add IsEmbedded and fix doc about Value() --- field.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/field.go b/field.go index 510ab71..cd751ca 100644 --- a/field.go +++ b/field.go @@ -2,19 +2,25 @@ package structure import "reflect" -// Field represents a single struct field that encapsulates many high level -// function around a singel struct field +// Field represents a single struct field that encapsulates high level +// functions around the field. type Field struct { value reflect.Value field reflect.StructField } // Tag returns the value associated with key in the tag string. If there is no -// such key in the tag, Tag returns the empty string +// such key in the tag, Tag returns the empty string. func (f *Field) Tag(key string) string { return f.field.Tag.Get(key) } +// Value returns the underlying value of of the field. func (f *Field) Value() interface{} { return f.value.Interface() } + +// IsEmbedded returns true if the given field is an anonymous field (embedded) +func (f *Field) IsEmbedded() bool { + return f.field.Anonymous +} From d5c544c4f6d486002275ff8c8f6e1bf7cf3c59f6 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Sun, 10 Aug 2014 16:05:37 +0300 Subject: [PATCH 03/10] field: add tests for fields, WIP --- field.go | 8 +++++++- field_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 field_test.go diff --git a/field.go b/field.go index cd751ca..952eeb8 100644 --- a/field.go +++ b/field.go @@ -15,7 +15,8 @@ func (f *Field) Tag(key string) string { return f.field.Tag.Get(key) } -// Value returns the underlying value of of the field. +// Value returns the underlying value of of the field. It panics if the field +// is not exported. func (f *Field) Value() interface{} { return f.value.Interface() } @@ -24,3 +25,8 @@ func (f *Field) Value() interface{} { func (f *Field) IsEmbedded() bool { return f.field.Anonymous } + +// IsExported returns true if the given field is exported. +func (f *Field) IsExported() bool { + return f.field.PkgPath == "" +} diff --git a/field_test.go b/field_test.go new file mode 100644 index 0000000..8c24760 --- /dev/null +++ b/field_test.go @@ -0,0 +1,49 @@ +package structure + +import "testing" + +// A test struct that defines all cases +type Foo struct { + A string + B int `structure:"y"` + C bool `json:"c"` + d string // not exported + *Bar // embedded +} + +type Bar struct { + E string + F int + g []string +} + +func newStruct() *Struct { + b := &Bar{ + E: "example", + F: 2, + g: []string{"zeynep", "fatih"}, + } + + f := &Foo{ + A: "gopher", + B: 1, + C: true, + d: "small", + } + f.Bar = b + + return New(f) +} + +func TestField(t *testing.T) { + s := newStruct() + + defer func() { + err := recover() + if err == nil { + t.Error("Retrieveing a non existing field from the struct should panic") + } + }() + + _ = s.Field("no-field") +} From 84a0f611c765b012427606b35910e467268e9938 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Sun, 10 Aug 2014 16:42:04 +0300 Subject: [PATCH 04/10] more tests, additional methods for fields --- field.go | 14 +++++++++++++ field_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++- structs.go | 11 +++------- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/field.go b/field.go index 952eeb8..1a21071 100644 --- a/field.go +++ b/field.go @@ -30,3 +30,17 @@ func (f *Field) IsEmbedded() bool { func (f *Field) IsExported() bool { return f.field.PkgPath == "" } + +// IsZero returns true if the given field is not initalized (has a zero value). +// It panics if the field is not exported. +func (f *Field) IsZero() bool { + zero := reflect.Zero(f.value.Type()).Interface() + current := f.Value() + + return reflect.DeepEqual(current, zero) +} + +// Name returns the name of the given field +func (f *Field) Name() string { + return f.field.Name +} diff --git a/field_test.go b/field_test.go index 8c24760..3f93f68 100644 --- a/field_test.go +++ b/field_test.go @@ -8,6 +8,7 @@ type Foo struct { B int `structure:"y"` C bool `json:"c"` d string // not exported + x string `xml:"x"` // not exported, with tag *Bar // embedded } @@ -24,9 +25,9 @@ func newStruct() *Struct { g: []string{"zeynep", "fatih"}, } + // B and x is not initialized for testing f := &Foo{ A: "gopher", - B: 1, C: true, d: "small", } @@ -47,3 +48,56 @@ func TestField(t *testing.T) { _ = s.Field("no-field") } + +func TestField_Tag(t *testing.T) { + s := newStruct() + + v := s.Field("B").Tag("json") + if v != "" { + t.Errorf("Field's tag value of a non existing tag should return empty, got: %s", v) + } + + v = s.Field("C").Tag("json") + if v != "c" { + t.Errorf("Field's tag value of the existing field C should return 'c', got: %s", v) + } + + v = s.Field("d").Tag("json") + if v != "" { + t.Errorf("Field's tag value of a non exported field should return empty, got: %s", v) + } + + v = s.Field("x").Tag("xml") + if v != "x" { + t.Errorf("Field's tag value of a non exported field with a tag should return 'x', got: %s", v) + } + + v = s.Field("A").Tag("json") + if v != "" { + t.Errorf("Field's tag value of a existing field without a tag should return empty, got: %s", v) + } +} + +func TestField_Value(t *testing.T) { + s := newStruct() + + v := s.Field("A").Value() + val, ok := v.(string) + if !ok { + t.Errorf("Field's value of a A should be string") + } + + if val != "gopher" { + t.Errorf("Field's value of a existing tag should return 'gopher', got: %s", val) + } + + defer func() { + err := recover() + if err == nil { + t.Error("Value of a non exported field from the field should panic") + } + }() + + // should panic + _ = s.Field("d").Value() +} diff --git a/structs.go b/structs.go index 87c1465..bc7249f 100644 --- a/structs.go +++ b/structs.go @@ -32,8 +32,7 @@ func (s *Struct) Fields() []string { } // Field returns a new Field struct that provides several high level functions -// around a single struct field entitiy. It panics if the field is not found or -// is unexported. +// around a single struct field entitiy. It panics if the field is not found. func (s *Struct) Field(name string) *Field { f, ok := s.FieldOk(name) if !ok { @@ -44,8 +43,8 @@ func (s *Struct) Field(name string) *Field { } // Field returns a new Field struct that provides several high level functions -// around a single struct field entitiy and a boolean indicating if the field -// was found. It panics if the or is unexported. +// around a single struct field entitiy. The boolean returns true if the field +// was found. func (s *Struct) FieldOk(name string) (*Field, bool) { v := strctVal(s.raw) t := v.Type() @@ -55,10 +54,6 @@ func (s *Struct) FieldOk(name string) (*Field, bool) { return nil, false } - if field.PkgPath != "" { - panic("unexported field access is not allowed") - } - return &Field{ field: field, value: v.FieldByName(name), From a92e1f9f8d03954690f80ef86e04c54c534e9bc8 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Mon, 11 Aug 2014 00:51:56 +0300 Subject: [PATCH 05/10] field: cover all cases for fields --- field_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/field_test.go b/field_test.go index 3f93f68..d42d5af 100644 --- a/field_test.go +++ b/field_test.go @@ -101,3 +101,51 @@ func TestField_Value(t *testing.T) { // should panic _ = s.Field("d").Value() } + +func TestField_IsEmbedded(t *testing.T) { + s := newStruct() + + if !s.Field("Bar").IsEmbedded() { + t.Errorf("Fields 'Bar' field is an embedded field") + } + + if s.Field("d").IsEmbedded() { + t.Errorf("Fields 'd' field is not an embedded field") + } +} + +func TestField_IsExported(t *testing.T) { + s := newStruct() + + if !s.Field("Bar").IsExported() { + t.Errorf("Fields 'Bar' field is an exported field") + } + + if !s.Field("A").IsExported() { + t.Errorf("Fields 'A' field is an exported field") + } + + if s.Field("d").IsExported() { + t.Errorf("Fields 'd' field is not an exported field") + } +} + +func TestField_IsZero(t *testing.T) { + s := newStruct() + + if s.Field("A").IsZero() { + t.Errorf("Fields 'A' field is an initialized field") + } + + if !s.Field("B").IsZero() { + t.Errorf("Fields 'B' field is not an initialized field") + } +} + +func TestField_Name(t *testing.T) { + s := newStruct() + + if s.Field("A").Name() != "A" { + t.Errorf("Fields 'A' field should have the name 'A'") + } +} From 3bd57b6a66d15533aa31bd4d41b4593459ad9300 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Mon, 11 Aug 2014 01:00:39 +0300 Subject: [PATCH 06/10] field: add a method to get nested fields back --- field.go | 17 +++++++++++++++++ field_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/field.go b/field.go index 1a21071..d65d9f8 100644 --- a/field.go +++ b/field.go @@ -44,3 +44,20 @@ func (f *Field) IsZero() bool { func (f *Field) Name() string { return f.field.Name } + +// 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 { + v := strctVal(f.value.Interface()) + t := v.Type() + + field, ok := t.FieldByName(name) + if !ok { + panic("field not found") + } + + return &Field{ + field: field, + value: v.FieldByName(name), + } +} diff --git a/field_test.go b/field_test.go index d42d5af..c850643 100644 --- a/field_test.go +++ b/field_test.go @@ -149,3 +149,27 @@ func TestField_Name(t *testing.T) { t.Errorf("Fields 'A' field should have the name 'A'") } } + +func TestField_Field(t *testing.T) { + s := newStruct() + + e := s.Field("Bar").Field("E") + + val, ok := e.Value().(string) + if !ok { + t.Error("The value of the field 'e' inside 'Bar' struct should be string") + } + + if val != "example" { + t.Errorf("The value of 'e' should be 'example, got: %s", val) + } + + defer func() { + err := recover() + if err == nil { + t.Error("Field of a non existing nested struct should panic") + } + }() + + _ = s.Field("Bar").Field("e") +} From f614213600018ba88dbe8535a70392b4f77dc9ab Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Mon, 11 Aug 2014 01:52:22 +0300 Subject: [PATCH 07/10] struct: increase coverage back to %100 --- field.go | 15 ++- field_test.go | 23 +++++ structs.go | 84 ----------------- structure.go | 192 +++++++++++++++++++++++--------------- structure_example_test.go | 20 ---- structure_test.go | 29 +----- 6 files changed, 157 insertions(+), 206 deletions(-) delete mode 100644 structs.go diff --git a/field.go b/field.go index d65d9f8..bf2e761 100644 --- a/field.go +++ b/field.go @@ -48,16 +48,27 @@ func (f *Field) Name() string { // 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 { + field, ok := f.FieldOk(name) + if !ok { + panic("field not found") + } + + 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. +func (f *Field) FieldOk(name string) (*Field, bool) { v := strctVal(f.value.Interface()) t := v.Type() field, ok := t.FieldByName(name) if !ok { - panic("field not found") + return nil, false } return &Field{ field: field, value: v.FieldByName(name), - } + }, true } diff --git a/field_test.go b/field_test.go index c850643..beb892d 100644 --- a/field_test.go +++ b/field_test.go @@ -173,3 +173,26 @@ func TestField_Field(t *testing.T) { _ = s.Field("Bar").Field("e") } + +func TestField_FieldOk(t *testing.T) { + s := newStruct() + + b, ok := s.FieldOk("Bar") + if !ok { + t.Error("The field 'Bar' should exists.") + } + + e, ok := b.FieldOk("E") + if !ok { + t.Error("The field 'E' should exists.") + } + + val, ok := e.Value().(string) + if !ok { + t.Error("The value of the field 'e' inside 'Bar' struct should be string") + } + + if val != "example" { + t.Errorf("The value of 'e' should be 'example, got: %s", val) + } +} diff --git a/structs.go b/structs.go deleted file mode 100644 index bc7249f..0000000 --- a/structs.go +++ /dev/null @@ -1,84 +0,0 @@ -package structure - -// Struct encapsulates a struct type to provide several high level functions -// around the structure. -type Struct struct { - raw interface{} -} - -// New returns a new *Struct with the struct s. -func New(s interface{}) *Struct { - return &Struct{ - raw: s, - } -} - -// Map converts the given struct to a map[string]interface{}. For more info -// refer to Map() function. -func (s *Struct) Map() map[string]interface{} { - return Map(s.raw) -} - -// Values converts the given struct to a []interface{}. For more info refer to -// Values() function. -func (s *Struct) Values() []interface{} { - return Values(s.raw) -} - -// Fields returns a slice of field names For more info refer to Fields() -// function. -func (s *Struct) Fields() []string { - return Fields(s.raw) -} - -// Field returns a new Field struct that provides several high level functions -// around a single struct field entitiy. It panics if the field is not found. -func (s *Struct) Field(name string) *Field { - f, ok := s.FieldOk(name) - if !ok { - panic("field not found") - } - - return f -} - -// Field returns a new Field struct that provides several high level functions -// around a single struct field entitiy. The boolean returns true if the field -// was found. -func (s *Struct) FieldOk(name string) (*Field, bool) { - v := strctVal(s.raw) - t := v.Type() - - field, ok := t.FieldByName(name) - if !ok { - return nil, false - } - - return &Field{ - field: field, - value: v.FieldByName(name), - }, true -} - -// IsZero returns true if all fields is equal to a zero value. For more info -// refer to IsZero() function. -func (s *Struct) IsZero() bool { - return IsZero(s.raw) -} - -// HasZero returns true if any field is equal to a zero value. For more info -// refer to HasZero() function. -func (s *Struct) HasZero() bool { - return HasZero(s.raw) -} - -// Name returns the structs's type name within its package. For more info refer -// to Name() function. -func (s *Struct) Name() string { - return Name(s) -} - -// IsStruct returns true if its a struct or a pointer to struct. -func (s *Struct) IsStruct() bool { - return IsStruct(s.raw) -} diff --git a/structure.go b/structure.go index c4b32ae..844d1fe 100644 --- a/structure.go +++ b/structure.go @@ -10,7 +10,23 @@ var ( DefaultTagName = "structure" // struct's field default tag name ) -// Map converts the given s struct to a map[string]interface{}, where the keys +// Struct encapsulates a struct type to provide several high level functions +// around the structure. +type Struct struct { + raw interface{} + value reflect.Value +} + +// New returns a new *Struct with the struct s. It panics if the s's kind is +// not struct. +func New(s interface{}) *Struct { + return &Struct{ + raw: s, + value: strctVal(s), + } +} + +// Map converts the given struct to a map[string]interface{}, where the keys // of the map are the field names and the values of the map the associated // values of the fields. The default key string is the struct field name but // can be changed in the struct field's tag value. The "structure" key in the @@ -32,15 +48,15 @@ var ( // 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 Map(s interface{}) map[string]interface{} { +// fields will be neglected. +func (s *Struct) Map() map[string]interface{} { out := make(map[string]interface{}) - v, fields := strctInfo(s) + fields := s.structFields() for _, field := range fields { name := field.Name - val := v.FieldByName(name) + val := s.value.FieldByName(name) var finalVal interface{} @@ -79,14 +95,14 @@ func Map(s interface{}) map[string]interface{} { // 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 Values(s interface{}) []interface{} { - v, fields := strctInfo(s) +// fields will be neglected. +func (s *Struct) Values() []interface{} { + fields := s.structFields() var t []interface{} for _, field := range fields { - val := v.FieldByName(field.Name) + val := s.value.FieldByName(field.Name) _, tagOpts := parseTag(field.Tag.Get(DefaultTagName)) @@ -102,7 +118,6 @@ func Values(s interface{}) []interface{} { } return t - } // Fields returns a slice of field names. A struct tag with the content of "-" @@ -120,13 +135,13 @@ func Values(s interface{}) []interface{} { // // 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 Fields(s interface{}) []string { - v, fields := strctInfo(s) +func (s *Struct) Fields() []string { + fields := s.structFields() var keys []string for _, field := range fields { - val := v.FieldByName(field.Name) + val := s.value.FieldByName(field.Name) _, tagOpts := parseTag(field.Tag.Get(DefaultTagName)) @@ -144,6 +159,34 @@ func Fields(s interface{}) []string { return keys } +// Field returns a new Field struct that provides several high level functions +// around a single struct field entitiy. It panics if the field is not found. +func (s *Struct) Field(name string) *Field { + f, ok := s.FieldOk(name) + if !ok { + panic("field not found") + } + + return f +} + +// Field returns a new Field struct that provides several high level functions +// around a single struct field entitiy. The boolean returns true if the field +// was found. +func (s *Struct) FieldOk(name string) (*Field, bool) { + t := s.value.Type() + + field, ok := t.FieldByName(name) + if !ok { + return nil, false + } + + return &Field{ + field: field, + value: s.value.FieldByName(name), + }, true +} + // IsZero returns true if all fields in a struct is a zero value (not // initialized) A struct tag with the content of "-" ignores the checking of // that particular field. Example: @@ -160,11 +203,11 @@ func Fields(s interface{}) []string { // // 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 IsZero(s interface{}) bool { - v, fields := strctInfo(s) +func (s *Struct) IsZero() bool { + fields := s.structFields() for _, field := range fields { - val := v.FieldByName(field.Name) + val := s.value.FieldByName(field.Name) _, tagOpts := parseTag(field.Tag.Get(DefaultTagName)) @@ -207,11 +250,11 @@ func IsZero(s interface{}) bool { // // 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 HasZero(s interface{}) bool { - v, fields := strctInfo(s) +func (s *Struct) HasZero() bool { + fields := s.structFields() for _, field := range fields { - val := v.FieldByName(field.Name) + val := s.value.FieldByName(field.Name) _, tagOpts := parseTag(field.Tag.Get(DefaultTagName)) @@ -238,61 +281,17 @@ func HasZero(s interface{}) bool { return false } -// IsStruct returns true if the given variable is a struct or a pointer to -// struct. -func IsStruct(s interface{}) bool { - t := reflect.TypeOf(s) - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - - return t.Kind() == reflect.Struct +// Name returns the structs's type name within its package. For more info refer +// to Name() function. +func (s *Struct) Name() string { + return s.value.Type().Name() } -// Name returns the structs's type name within its package. It returns an -// empty string for unnamed types. It panics if s's kind is not struct. -func Name(s interface{}) string { - t := reflect.TypeOf(s) - - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - - if t.Kind() != reflect.Struct { - panic("not struct") - } - - return t.Name() -} - -// Has returns true if the given field name exists for the struct s. It panic's -// if s's kind is not struct. -func Has(s interface{}, fieldName string) bool { - v, fields := strctInfo(s) - - for _, field := range fields { - val := v.FieldByName(field.Name) - - if IsStruct(val.Interface()) { - if ok := Has(val.Interface(), fieldName); ok { - return true - } - } - - if field.Name == fieldName { - return true - } - } - - return false -} - -// strctInfo returns the struct value and the exported struct fields for a -// given s struct. This is a convenient helper method to avoid duplicate code -// in some of the functions. -func strctInfo(s interface{}) (reflect.Value, []reflect.StructField) { - v := strctVal(s) - t := v.Type() +// structFields returns the exported struct fields for a given s struct. This +// is a convenient helper method to avoid duplicate code in some of the +// functions. +func (s *Struct) structFields() []reflect.StructField { + t := s.value.Type() var f []reflect.StructField @@ -311,7 +310,7 @@ func strctInfo(s interface{}) (reflect.Value, []reflect.StructField) { f = append(f, field) } - return v, f + return f } func strctVal(s interface{}) reflect.Value { @@ -328,3 +327,50 @@ func strctVal(s interface{}) reflect.Value { return v } + +// Map converts the given struct to a map[string]interface{}. For more info +// refer to Struct types Map() method. It panics if s's kind is not struct. +func Map(s interface{}) map[string]interface{} { + return New(s).Map() +} + +// Values converts the given struct to a []interface{}. For more info refer to +// Struct types Values() method. It panics if s's kind is not struct. +func Values(s interface{}) []interface{} { + return New(s).Values() +} + +// Fields returns a slice of field names. For more info refer to Struct types +// Fields() method. It panics if s's kind is not struct. +func Fields(s interface{}) []string { + return New(s).Fields() +} + +// IsZero returns true if all fields is equal to a zero value. For more info +// refer to Struct types IsZero() method. It panics if s's kind is not struct. +func IsZero(s interface{}) bool { + return New(s).IsZero() +} + +// HasZero returns true if any field is equal to a zero value. For more info +// refer to Struct types HasZero() method. It panics if s's kind is not struct. +func HasZero(s interface{}) bool { + return New(s).HasZero() +} + +// IsStruct returns true if the given variable is a struct or a pointer to +// struct. +func IsStruct(s interface{}) bool { + t := reflect.TypeOf(s) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + return t.Kind() == reflect.Struct +} + +// Name returns the structs's type name within its package. It returns an +// empty string for unnamed types. It panics if s's kind is not struct. +func Name(s interface{}) string { + return New(s).Name() +} diff --git a/structure_example_test.go b/structure_example_test.go index 91ca3da..2f59374 100644 --- a/structure_example_test.go +++ b/structure_example_test.go @@ -237,23 +237,3 @@ func ExampleHasZero() { // true // false } - -func ExampleHas() { - type Access struct { - Name string - LastAccessed time.Time - Number int - } - - s := &Access{ - Name: "Fatih", - LastAccessed: time.Now(), - Number: 1234567, - } - - found := Has(s, "LastAccessed") - - fmt.Printf("Has: %+v\n", found) - // Output: - // Has: true -} diff --git a/structure_test.go b/structure_test.go index 4684f49..da800cd 100644 --- a/structure_test.go +++ b/structure_test.go @@ -1,6 +1,7 @@ package structure import ( + "fmt" "reflect" "testing" "time" @@ -29,6 +30,7 @@ func TestStructIndexes(t *testing.T) { defer func() { err := recover() if err != nil { + fmt.Printf("err %+v\n", err) t.Error("Using mixed indexes should not panic") } }() @@ -735,33 +737,6 @@ func TestHasZero_Anonymous(t *testing.T) { } } -func TestHas(t *testing.T) { - type A struct { - Name string - D string - } - a := A{Name: "example"} - - type B struct { - A - C int - } - b := &B{C: 123} - b.A = a - - if !Has(b, "Name") { - t.Error("Has should return true for Name, but it's false") - } - - if Has(b, "NotAvailable") { - t.Error("Has should return false for NotAvailable, but it's true") - } - - if !Has(b, "C") { - t.Error("Has should return true for C, but it's false") - } -} - func TestName(t *testing.T) { type Foo struct { A string From 1c91005c39df2f7132b95cf2a7fa3e524601a50a Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Mon, 11 Aug 2014 03:53:35 +0300 Subject: [PATCH 08/10] structure: more improvements --- README.md | 116 +++++++++++++++++++++++++++++++------- field.go | 5 +- structure.go | 46 +++++++-------- structure_example_test.go | 27 ++++++--- structure_test.go | 48 +++------------- 5 files changed, 144 insertions(+), 98 deletions(-) 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) } From d06e3eb5fe086981139df9e83d0918ec1dd90b07 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Mon, 11 Aug 2014 04:15:58 +0300 Subject: [PATCH 09/10] structure: improve examples --- README.md | 24 ++++++++-------- structure_example_test.go | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index bd424f9..e1e5567 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,10 @@ m := s.Map() // => ["gopher", 123456, true] v := s.Values() +// Convert the values of a struct to a []*Field +// (see "Field methods" for more info about fields) +f := s.Fields() + // Check if any field of a struct is initialized or not. if s.HasZero() { fmt.Println("s has a zero value field") @@ -67,19 +71,13 @@ 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") -} - +m := structure.Map(s) // Get a map[string]interface{} +v := structure.Values(s) // Get a []interface{} +f := structure.Fields(s) // Get a []*Field +n := structure.Name(s) // Get the struct name +h := structure.HasZero(s) // Check if any field is initialized +z := structure.IsZero(s) // Check if all fields are initialized +i := structure.IsStruct(s) // Check if s is a struct or a pointer to struct ``` ### Field methods diff --git a/structure_example_test.go b/structure_example_test.go index 02eacb7..18d29cc 100644 --- a/structure_example_test.go +++ b/structure_example_test.go @@ -5,6 +5,31 @@ import ( "time" ) +func ExampleNew() { + type Server struct { + Name string + ID int32 + Enabled bool + } + + server := &Server{ + Name: "Arslan", + ID: 123456, + Enabled: true, + } + + s := New(server) + + fmt.Printf("Name : %v\n", s.Name()) + fmt.Printf("Values : %v\n", s.Values()) + fmt.Printf("Value of ID : %v\n", s.Field("ID").Value()) + // Output: + // Name : Server + // Values : [Arslan 123456 true] + // Value of ID : 123456 + +} + func ExampleMap() { type Server struct { Name string @@ -191,6 +216,40 @@ func ExampleFields_nested() { // Access.Person.Name: fatih } +func ExampleField() { + type Person struct { + Name string + Number int + } + + type Access struct { + Person Person + HasPermission bool + LastAccessed time.Time + } + + access := &Access{ + Person: Person{Name: "fatih", Number: 1234567}, + LastAccessed: time.Now(), + HasPermission: true, + } + + // Create a new Struct type + s := New(access) + + // Get the Field type for "Person" field + p := s.Field("Person") + + // Get the underlying "Name field" and print the value of it + name := p.Field("Name") + + fmt.Printf("Value of Person.Access.Name: %+v\n", name.Value()) + + // Output: + // Value of Person.Access.Name: fatih + +} + func ExampleIsZero() { type Server struct { Name string From 353ac3c16430c197d7c610255a064b1f2774b81a Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Mon, 11 Aug 2014 04:17:19 +0300 Subject: [PATCH 10/10] Readme: small typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1e5567..b5ecbfa 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ for _, f := range fields { if f.IsExported() { fmt.Printf("value : %+v\n", f.Value()) - fmt.Printf("is zero : %+v\n", f.Value()) + fmt.Printf("is zero : %+v\n", f.IsZero()) } } ```