diff --git a/README.md b/README.md index 2be6d3f..f61d1b9 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,16 @@ n := structure.Name(s) // => true h := structure.Has(s, "Enabled") -// Check if any field of a struct is initialized or not. +// Check if a field of a struct is initialized or not. if structure.HasZero(s) { fmt.Println("s has a zero value field") } +// Check if all field of a struct is initialized or not. +if structure.IsZero(s) { + fmt.Println("all fields of s is zero value") +} + // Check if it's a struct or a pointer to struct if structure.IsStruct(s) { fmt.Println("s is a struct") diff --git a/structure.go b/structure.go index 9b9a16d..4004a94 100644 --- a/structure.go +++ b/structure.go @@ -80,6 +80,43 @@ func Values(s interface{}) []interface{} { } +// 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: +// +// // Field is ignored by this package. +// Field bool `structure:"-"` +// +// 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) + + for i := range fields { + val := v.Field(i) + if IsStruct(val.Interface()) { + ok := IsZero(val.Interface()) + if !ok { + return false + } + + continue + } + + // zero value of the given field, such as "" for string, 0 for int + zero := reflect.Zero(v.Field(i).Type()).Interface() + + // current value of the given field + current := v.Field(i).Interface() + + if !reflect.DeepEqual(current, zero) { + return false + } + } + + return true +} + // HasZero returns true if a field in a struct is not initialized (zero value). // A struct tag with the content of "-" ignores the checking of that particular // field. Example: diff --git a/structure_example_test.go b/structure_example_test.go index 3babe71..6db6360 100644 --- a/structure_example_test.go +++ b/structure_example_test.go @@ -96,6 +96,31 @@ func ExampleFields() { // Fields: [Name LastAccessed Number] } +func ExampleIsZero() { + type Server struct { + Name string + ID int32 + Enabled bool + } + + // Nothing is initalized + a := &Server{} + isZeroA := IsZero(a) + + // Name and Enabled is initialized, but not ID + b := &Server{ + Name: "Golang", + Enabled: true, + } + isZeroB := IsZero(b) + + fmt.Printf("%#v\n", isZeroA) + fmt.Printf("%#v\n", isZeroB) + // Output: + // true + // false +} + func ExampleHasZero() { // Let's define an Access struct. Note that the "Enabled" field is not // going to be checked because we added the "structure" tag to the field. diff --git a/structure_test.go b/structure_test.go index 94f03dc..b2e8838 100644 --- a/structure_test.go +++ b/structure_test.go @@ -345,6 +345,102 @@ func TestFields_Anonymous(t *testing.T) { } } +func TestIsZero(t *testing.T) { + var T = struct { + A string + B int + C bool `structure:"-"` + D []string + }{} + + ok := IsZero(T) + if !ok { + t.Error("IsZero should return true because none of the fields are initialized.") + } + + var X = struct { + A string + F *bool + }{ + A: "a-value", + } + + ok = IsZero(X) + if ok { + t.Error("IsZero should return false because A is initialized") + } + + var Y = struct { + A string + B int + }{ + A: "a-value", + B: 123, + } + + ok = IsZero(Y) + if ok { + t.Error("IsZero should return false because A and B is initialized") + } +} + +func TestIsZero_Nested(t *testing.T) { + type A struct { + Name string + D string + } + a := A{Name: "example"} + + type B struct { + A A + C int + } + b := &B{A: a, C: 123} + + ok := IsZero(b) + if ok { + t.Error("IsZero should return false because A, B and C are initialized") + } + + aZero := A{} + bZero := &B{A: aZero} + + ok = IsZero(bZero) + if !ok { + t.Error("IsZero should return true because neither A nor B is initialized") + } + +} + +func TestIsZero_Anonymous(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 + + ok := IsZero(b) + if ok { + t.Error("IsZero should return false because A, B and C are initialized") + } + + aZero := A{} + bZero := &B{} + bZero.A = aZero + + ok = IsZero(bZero) + if !ok { + t.Error("IsZero should return true because neither A nor B is initialized") + } +} + func TestHasZero(t *testing.T) { var T = struct { A string