structure: more improvements

This commit is contained in:
Fatih Arslan 2014-08-11 03:53:35 +03:00
parent f614213600
commit 1c91005c39
5 changed files with 144 additions and 98 deletions

116
README.md
View File

@ -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)

View File

@ -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()

View File

@ -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()
}

View File

@ -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() {

View File

@ -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)
}