├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── field.go ├── field_test.go ├── structs.go ├── structs_example_test.go ├── structs_test.go ├── tags.go └── tags_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.7.x 4 | - 1.8.x 5 | - 1.9.x 6 | - tip 7 | sudo: false 8 | before_install: 9 | - go get github.com/axw/gocov/gocov 10 | - go get github.com/mattn/goveralls 11 | - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 12 | script: 13 | - $HOME/gopath/bin/goveralls -service=travis-ci 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Fatih Arslan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived project. No maintenance. 2 | 3 | This project is not maintained anymore and is archived. Feel free to fork and 4 | make your own changes if needed. For more detail read my blog post: [Taking an indefinite sabbatical from my projects](https://arslan.io/2018/10/09/taking-an-indefinite-sabbatical-from-my-projects/) 5 | 6 | Thanks to everyone for their valuable feedback and contributions. 7 | 8 | # Structs [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structs) [![Build Status](http://img.shields.io/travis/fatih/structs.svg?style=flat-square)](https://travis-ci.org/fatih/structs) [![Coverage Status](http://img.shields.io/coveralls/fatih/structs.svg?style=flat-square)](https://coveralls.io/r/fatih/structs) 9 | 10 | Structs contains various utilities to work with Go (Golang) structs. It was 11 | initially used by me to convert a struct into a `map[string]interface{}`. With 12 | time I've added other utilities for structs. It's basically a high level 13 | package based on primitives from the reflect package. Feel free to add new 14 | functions or improve the existing code. 15 | 16 | ## Install 17 | 18 | ```bash 19 | go get github.com/fatih/structs 20 | ``` 21 | 22 | ## Usage and Examples 23 | 24 | Just like the standard lib `strings`, `bytes` and co packages, `structs` has 25 | many global functions to manipulate or organize your struct data. Lets define 26 | and declare a struct: 27 | 28 | ```go 29 | type Server struct { 30 | Name string `json:"name,omitempty"` 31 | ID int 32 | Enabled bool 33 | users []string // not exported 34 | http.Server // embedded 35 | } 36 | 37 | server := &Server{ 38 | Name: "gopher", 39 | ID: 123456, 40 | Enabled: true, 41 | } 42 | ``` 43 | 44 | ```go 45 | // Convert a struct to a map[string]interface{} 46 | // => {"Name":"gopher", "ID":123456, "Enabled":true} 47 | m := structs.Map(server) 48 | 49 | // Convert the values of a struct to a []interface{} 50 | // => ["gopher", 123456, true] 51 | v := structs.Values(server) 52 | 53 | // Convert the names of a struct to a []string 54 | // (see "Names methods" for more info about fields) 55 | n := structs.Names(server) 56 | 57 | // Convert the values of a struct to a []*Field 58 | // (see "Field methods" for more info about fields) 59 | f := structs.Fields(server) 60 | 61 | // Return the struct name => "Server" 62 | n := structs.Name(server) 63 | 64 | // Check if any field of a struct is initialized or not. 65 | h := structs.HasZero(server) 66 | 67 | // Check if all fields of a struct is initialized or not. 68 | z := structs.IsZero(server) 69 | 70 | // Check if server is a struct or a pointer to struct 71 | i := structs.IsStruct(server) 72 | ``` 73 | 74 | ### Struct methods 75 | 76 | The structs functions can be also used as independent methods by creating a new 77 | `*structs.Struct`. This is handy if you want to have more control over the 78 | structs (such as retrieving a single Field). 79 | 80 | ```go 81 | // Create a new struct type: 82 | s := structs.New(server) 83 | 84 | m := s.Map() // Get a map[string]interface{} 85 | v := s.Values() // Get a []interface{} 86 | f := s.Fields() // Get a []*Field 87 | n := s.Names() // Get a []string 88 | f := s.Field(name) // Get a *Field based on the given field name 89 | f, ok := s.FieldOk(name) // Get a *Field based on the given field name 90 | n := s.Name() // Get the struct name 91 | h := s.HasZero() // Check if any field is uninitialized 92 | z := s.IsZero() // Check if all fields are uninitialized 93 | ``` 94 | 95 | ### Field methods 96 | 97 | We can easily examine a single Field for more detail. Below you can see how we 98 | get and interact with various field methods: 99 | 100 | 101 | ```go 102 | s := structs.New(server) 103 | 104 | // Get the Field struct for the "Name" field 105 | name := s.Field("Name") 106 | 107 | // Get the underlying value, value => "gopher" 108 | value := name.Value().(string) 109 | 110 | // Set the field's value 111 | name.Set("another gopher") 112 | 113 | // Get the field's kind, kind => "string" 114 | name.Kind() 115 | 116 | // Check if the field is exported or not 117 | if name.IsExported() { 118 | fmt.Println("Name field is exported") 119 | } 120 | 121 | // Check if the value is a zero value, such as "" for string, 0 for int 122 | if !name.IsZero() { 123 | fmt.Println("Name is initialized") 124 | } 125 | 126 | // Check if the field is an anonymous (embedded) field 127 | if !name.IsEmbedded() { 128 | fmt.Println("Name is not an embedded field") 129 | } 130 | 131 | // Get the Field's tag value for tag name "json", tag value => "name,omitempty" 132 | tagValue := name.Tag("json") 133 | ``` 134 | 135 | Nested structs are supported too: 136 | 137 | ```go 138 | addrField := s.Field("Server").Field("Addr") 139 | 140 | // Get the value for addr 141 | a := addrField.Value().(string) 142 | 143 | // Or get all fields 144 | httpServer := s.Field("Server").Fields() 145 | ``` 146 | 147 | We can also get a slice of Fields from the Struct type to iterate over all 148 | fields. This is handy if you wish to examine all fields: 149 | 150 | ```go 151 | s := structs.New(server) 152 | 153 | for _, f := range s.Fields() { 154 | fmt.Printf("field name: %+v\n", f.Name()) 155 | 156 | if f.IsExported() { 157 | fmt.Printf("value : %+v\n", f.Value()) 158 | fmt.Printf("is zero : %+v\n", f.IsZero()) 159 | } 160 | } 161 | ``` 162 | 163 | ## Credits 164 | 165 | * [Fatih Arslan](https://github.com/fatih) 166 | * [Cihangir Savas](https://github.com/cihangir) 167 | 168 | ## License 169 | 170 | The MIT License (MIT) - see LICENSE.md for more details 171 | -------------------------------------------------------------------------------- /field.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | var ( 10 | errNotExported = errors.New("field is not exported") 11 | errNotSettable = errors.New("field is not settable") 12 | ) 13 | 14 | // Field represents a single struct field that encapsulates high level 15 | // functions around the field. 16 | type Field struct { 17 | value reflect.Value 18 | field reflect.StructField 19 | defaultTag string 20 | } 21 | 22 | // Tag returns the value associated with key in the tag string. If there is no 23 | // such key in the tag, Tag returns the empty string. 24 | func (f *Field) Tag(key string) string { 25 | return f.field.Tag.Get(key) 26 | } 27 | 28 | // Value returns the underlying value of the field. It panics if the field 29 | // is not exported. 30 | func (f *Field) Value() interface{} { 31 | return f.value.Interface() 32 | } 33 | 34 | // IsEmbedded returns true if the given field is an anonymous field (embedded) 35 | func (f *Field) IsEmbedded() bool { 36 | return f.field.Anonymous 37 | } 38 | 39 | // IsExported returns true if the given field is exported. 40 | func (f *Field) IsExported() bool { 41 | return f.field.PkgPath == "" 42 | } 43 | 44 | // IsZero returns true if the given field is not initialized (has a zero value). 45 | // It panics if the field is not exported. 46 | func (f *Field) IsZero() bool { 47 | zero := reflect.Zero(f.value.Type()).Interface() 48 | current := f.Value() 49 | 50 | return reflect.DeepEqual(current, zero) 51 | } 52 | 53 | // Name returns the name of the given field 54 | func (f *Field) Name() string { 55 | return f.field.Name 56 | } 57 | 58 | // Kind returns the fields kind, such as "string", "map", "bool", etc .. 59 | func (f *Field) Kind() reflect.Kind { 60 | return f.value.Kind() 61 | } 62 | 63 | // Set sets the field to given value v. It returns an error if the field is not 64 | // settable (not addressable or not exported) or if the given value's type 65 | // doesn't match the fields type. 66 | func (f *Field) Set(val interface{}) error { 67 | // we can't set unexported fields, so be sure this field is exported 68 | if !f.IsExported() { 69 | return errNotExported 70 | } 71 | 72 | // do we get here? not sure... 73 | if !f.value.CanSet() { 74 | return errNotSettable 75 | } 76 | 77 | given := reflect.ValueOf(val) 78 | 79 | if f.value.Kind() != given.Kind() { 80 | return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind()) 81 | } 82 | 83 | f.value.Set(given) 84 | return nil 85 | } 86 | 87 | // Zero sets the field to its zero value. It returns an error if the field is not 88 | // settable (not addressable or not exported). 89 | func (f *Field) Zero() error { 90 | zero := reflect.Zero(f.value.Type()).Interface() 91 | return f.Set(zero) 92 | } 93 | 94 | // Fields returns a slice of Fields. This is particular handy to get the fields 95 | // of a nested struct . A struct tag with the content of "-" ignores the 96 | // checking of that particular field. Example: 97 | // 98 | // // Field is ignored by this package. 99 | // Field *http.Request `structs:"-"` 100 | // 101 | // It panics if field is not exported or if field's kind is not struct 102 | func (f *Field) Fields() []*Field { 103 | return getFields(f.value, f.defaultTag) 104 | } 105 | 106 | // Field returns the field from a nested struct. It panics if the nested struct 107 | // is not exported or if the field was not found. 108 | func (f *Field) Field(name string) *Field { 109 | field, ok := f.FieldOk(name) 110 | if !ok { 111 | panic("field not found") 112 | } 113 | 114 | return field 115 | } 116 | 117 | // FieldOk returns the field from a nested struct. The boolean returns whether 118 | // the field was found (true) or not (false). 119 | func (f *Field) FieldOk(name string) (*Field, bool) { 120 | value := &f.value 121 | // value must be settable so we need to make sure it holds the address of the 122 | // variable and not a copy, so we can pass the pointer to strctVal instead of a 123 | // copy (which is not assigned to any variable, hence not settable). 124 | // see "https://blog.golang.org/laws-of-reflection#TOC_8." 125 | if f.value.Kind() != reflect.Ptr { 126 | a := f.value.Addr() 127 | value = &a 128 | } 129 | v := strctVal(value.Interface()) 130 | t := v.Type() 131 | 132 | field, ok := t.FieldByName(name) 133 | if !ok { 134 | return nil, false 135 | } 136 | 137 | return &Field{ 138 | field: field, 139 | value: v.FieldByName(name), 140 | }, true 141 | } 142 | -------------------------------------------------------------------------------- /field_test.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | // A test struct that defines all cases 9 | type Foo struct { 10 | A string 11 | B int `structs:"y"` 12 | C bool `json:"c"` 13 | d string // not exported 14 | E *Baz 15 | x string `xml:"x"` // not exported, with tag 16 | Y []string 17 | Z map[string]interface{} 18 | *Bar // embedded 19 | } 20 | 21 | type Baz struct { 22 | A string 23 | B int 24 | } 25 | 26 | type Bar struct { 27 | E string 28 | F int 29 | g []string 30 | } 31 | 32 | func newStruct() *Struct { 33 | b := &Bar{ 34 | E: "example", 35 | F: 2, 36 | g: []string{"zeynep", "fatih"}, 37 | } 38 | 39 | // B and x is not initialized for testing 40 | f := &Foo{ 41 | A: "gopher", 42 | C: true, 43 | d: "small", 44 | E: nil, 45 | Y: []string{"example"}, 46 | Z: nil, 47 | } 48 | f.Bar = b 49 | 50 | return New(f) 51 | } 52 | 53 | func TestField_Set(t *testing.T) { 54 | s := newStruct() 55 | 56 | f := s.Field("A") 57 | err := f.Set("fatih") 58 | if err != nil { 59 | t.Error(err) 60 | } 61 | 62 | if f.Value().(string) != "fatih" { 63 | t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih") 64 | } 65 | 66 | f = s.Field("Y") 67 | err = f.Set([]string{"override", "with", "this"}) 68 | if err != nil { 69 | t.Error(err) 70 | } 71 | 72 | sliceLen := len(f.Value().([]string)) 73 | if sliceLen != 3 { 74 | t.Errorf("Setted values slice length is wrong: %d, want: %d", sliceLen, 3) 75 | } 76 | 77 | f = s.Field("C") 78 | err = f.Set(false) 79 | if err != nil { 80 | t.Error(err) 81 | } 82 | 83 | if f.Value().(bool) { 84 | t.Errorf("Setted value is wrong: %t want: %t", f.Value().(bool), false) 85 | } 86 | 87 | // let's pass a different type 88 | f = s.Field("A") 89 | err = f.Set(123) // Field A is of type string, but we are going to pass an integer 90 | if err == nil { 91 | t.Error("Setting a field's value with a different type than the field's type should return an error") 92 | } 93 | 94 | // old value should be still there :) 95 | if f.Value().(string) != "fatih" { 96 | t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih") 97 | } 98 | 99 | // let's access an unexported field, which should give an error 100 | f = s.Field("d") 101 | err = f.Set("large") 102 | if err != errNotExported { 103 | t.Error(err) 104 | } 105 | 106 | // let's set a pointer to struct 107 | b := &Bar{ 108 | E: "gopher", 109 | F: 2, 110 | } 111 | 112 | f = s.Field("Bar") 113 | err = f.Set(b) 114 | if err != nil { 115 | t.Error(err) 116 | } 117 | 118 | baz := &Baz{ 119 | A: "helloWorld", 120 | B: 42, 121 | } 122 | 123 | f = s.Field("E") 124 | err = f.Set(baz) 125 | if err != nil { 126 | t.Error(err) 127 | } 128 | 129 | ba := s.Field("E").Value().(*Baz) 130 | 131 | if ba.A != "helloWorld" { 132 | t.Errorf("could not set baz. Got: %s Want: helloWorld", ba.A) 133 | } 134 | } 135 | 136 | func TestField_NotSettable(t *testing.T) { 137 | a := map[int]Baz{ 138 | 4: { 139 | A: "value", 140 | }, 141 | } 142 | 143 | s := New(a[4]) 144 | 145 | if err := s.Field("A").Set("newValue"); err != errNotSettable { 146 | t.Errorf("Trying to set non-settable field should error with %q. Got %q instead.", errNotSettable, err) 147 | } 148 | } 149 | 150 | func TestField_Zero(t *testing.T) { 151 | s := newStruct() 152 | 153 | f := s.Field("A") 154 | err := f.Zero() 155 | if err != nil { 156 | t.Error(err) 157 | } 158 | 159 | if f.Value().(string) != "" { 160 | t.Errorf("Zeroed value is wrong: %s want: %s", f.Value().(string), "") 161 | } 162 | 163 | f = s.Field("Y") 164 | err = f.Zero() 165 | if err != nil { 166 | t.Error(err) 167 | } 168 | 169 | sliceLen := len(f.Value().([]string)) 170 | if sliceLen != 0 { 171 | t.Errorf("Zeroed values slice length is wrong: %d, want: %d", sliceLen, 0) 172 | } 173 | 174 | f = s.Field("C") 175 | err = f.Zero() 176 | if err != nil { 177 | t.Error(err) 178 | } 179 | 180 | if f.Value().(bool) { 181 | t.Errorf("Zeroed value is wrong: %t want: %t", f.Value().(bool), false) 182 | } 183 | 184 | // let's access an unexported field, which should give an error 185 | f = s.Field("d") 186 | err = f.Zero() 187 | if err != errNotExported { 188 | t.Error(err) 189 | } 190 | 191 | f = s.Field("Bar") 192 | err = f.Zero() 193 | if err != nil { 194 | t.Error(err) 195 | } 196 | 197 | f = s.Field("E") 198 | err = f.Zero() 199 | if err != nil { 200 | t.Error(err) 201 | } 202 | 203 | v := s.Field("E").value 204 | if !v.IsNil() { 205 | t.Errorf("could not set baz. Got: %s Want: ", v.Interface()) 206 | } 207 | } 208 | 209 | func TestField(t *testing.T) { 210 | s := newStruct() 211 | 212 | defer func() { 213 | err := recover() 214 | if err == nil { 215 | t.Error("Retrieveing a non existing field from the struct should panic") 216 | } 217 | }() 218 | 219 | _ = s.Field("no-field") 220 | } 221 | 222 | func TestField_Kind(t *testing.T) { 223 | s := newStruct() 224 | 225 | f := s.Field("A") 226 | if f.Kind() != reflect.String { 227 | t.Errorf("Field A has wrong kind: %s want: %s", f.Kind(), reflect.String) 228 | } 229 | 230 | f = s.Field("B") 231 | if f.Kind() != reflect.Int { 232 | t.Errorf("Field B has wrong kind: %s want: %s", f.Kind(), reflect.Int) 233 | } 234 | 235 | // unexported 236 | f = s.Field("d") 237 | if f.Kind() != reflect.String { 238 | t.Errorf("Field d has wrong kind: %s want: %s", f.Kind(), reflect.String) 239 | } 240 | } 241 | 242 | func TestField_Tag(t *testing.T) { 243 | s := newStruct() 244 | 245 | v := s.Field("B").Tag("json") 246 | if v != "" { 247 | t.Errorf("Field's tag value of a non existing tag should return empty, got: %s", v) 248 | } 249 | 250 | v = s.Field("C").Tag("json") 251 | if v != "c" { 252 | t.Errorf("Field's tag value of the existing field C should return 'c', got: %s", v) 253 | } 254 | 255 | v = s.Field("d").Tag("json") 256 | if v != "" { 257 | t.Errorf("Field's tag value of a non exported field should return empty, got: %s", v) 258 | } 259 | 260 | v = s.Field("x").Tag("xml") 261 | if v != "x" { 262 | t.Errorf("Field's tag value of a non exported field with a tag should return 'x', got: %s", v) 263 | } 264 | 265 | v = s.Field("A").Tag("json") 266 | if v != "" { 267 | t.Errorf("Field's tag value of a existing field without a tag should return empty, got: %s", v) 268 | } 269 | } 270 | 271 | func TestField_Value(t *testing.T) { 272 | s := newStruct() 273 | 274 | v := s.Field("A").Value() 275 | val, ok := v.(string) 276 | if !ok { 277 | t.Errorf("Field's value of a A should be string") 278 | } 279 | 280 | if val != "gopher" { 281 | t.Errorf("Field's value of a existing tag should return 'gopher', got: %s", val) 282 | } 283 | 284 | defer func() { 285 | err := recover() 286 | if err == nil { 287 | t.Error("Value of a non exported field from the field should panic") 288 | } 289 | }() 290 | 291 | // should panic 292 | _ = s.Field("d").Value() 293 | } 294 | 295 | func TestField_IsEmbedded(t *testing.T) { 296 | s := newStruct() 297 | 298 | if !s.Field("Bar").IsEmbedded() { 299 | t.Errorf("Fields 'Bar' field is an embedded field") 300 | } 301 | 302 | if s.Field("d").IsEmbedded() { 303 | t.Errorf("Fields 'd' field is not an embedded field") 304 | } 305 | } 306 | 307 | func TestField_IsExported(t *testing.T) { 308 | s := newStruct() 309 | 310 | if !s.Field("Bar").IsExported() { 311 | t.Errorf("Fields 'Bar' field is an exported field") 312 | } 313 | 314 | if !s.Field("A").IsExported() { 315 | t.Errorf("Fields 'A' field is an exported field") 316 | } 317 | 318 | if s.Field("d").IsExported() { 319 | t.Errorf("Fields 'd' field is not an exported field") 320 | } 321 | } 322 | 323 | func TestField_IsZero(t *testing.T) { 324 | s := newStruct() 325 | 326 | if s.Field("A").IsZero() { 327 | t.Errorf("Fields 'A' field is an initialized field") 328 | } 329 | 330 | if !s.Field("B").IsZero() { 331 | t.Errorf("Fields 'B' field is not an initialized field") 332 | } 333 | } 334 | 335 | func TestField_Name(t *testing.T) { 336 | s := newStruct() 337 | 338 | if s.Field("A").Name() != "A" { 339 | t.Errorf("Fields 'A' field should have the name 'A'") 340 | } 341 | } 342 | 343 | func TestField_Field(t *testing.T) { 344 | s := newStruct() 345 | 346 | e := s.Field("Bar").Field("E") 347 | 348 | val, ok := e.Value().(string) 349 | if !ok { 350 | t.Error("The value of the field 'e' inside 'Bar' struct should be string") 351 | } 352 | 353 | if val != "example" { 354 | t.Errorf("The value of 'e' should be 'example, got: %s", val) 355 | } 356 | 357 | defer func() { 358 | err := recover() 359 | if err == nil { 360 | t.Error("Field of a non existing nested struct should panic") 361 | } 362 | }() 363 | 364 | _ = s.Field("Bar").Field("e") 365 | } 366 | 367 | func TestField_Fields(t *testing.T) { 368 | s := newStruct() 369 | fields := s.Field("Bar").Fields() 370 | 371 | if len(fields) != 3 { 372 | t.Errorf("We expect 3 fields in embedded struct, was: %d", len(fields)) 373 | } 374 | } 375 | 376 | func TestField_FieldOk(t *testing.T) { 377 | s := newStruct() 378 | 379 | b, ok := s.FieldOk("Bar") 380 | if !ok { 381 | t.Error("The field 'Bar' should exists.") 382 | } 383 | 384 | e, ok := b.FieldOk("E") 385 | if !ok { 386 | t.Error("The field 'E' should exists.") 387 | } 388 | 389 | val, ok := e.Value().(string) 390 | if !ok { 391 | t.Error("The value of the field 'e' inside 'Bar' struct should be string") 392 | } 393 | 394 | if val != "example" { 395 | t.Errorf("The value of 'e' should be 'example, got: %s", val) 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | // Package structs contains various utilities functions to work with structs. 2 | package structs 3 | 4 | import ( 5 | "fmt" 6 | 7 | "reflect" 8 | ) 9 | 10 | var ( 11 | // DefaultTagName is the default tag name for struct fields which provides 12 | // a more granular to tweak certain structs. Lookup the necessary functions 13 | // for more info. 14 | DefaultTagName = "structs" // struct's field default tag name 15 | ) 16 | 17 | // Struct encapsulates a struct type to provide several high level functions 18 | // around the struct. 19 | type Struct struct { 20 | raw interface{} 21 | value reflect.Value 22 | TagName string 23 | } 24 | 25 | // New returns a new *Struct with the struct s. It panics if the s's kind is 26 | // not struct. 27 | func New(s interface{}) *Struct { 28 | return &Struct{ 29 | raw: s, 30 | value: strctVal(s), 31 | TagName: DefaultTagName, 32 | } 33 | } 34 | 35 | // Map converts the given struct to a map[string]interface{}, where the keys 36 | // of the map are the field names and the values of the map the associated 37 | // values of the fields. The default key string is the struct field name but 38 | // can be changed in the struct field's tag value. The "structs" key in the 39 | // struct's field tag value is the key name. Example: 40 | // 41 | // // Field appears in map as key "myName". 42 | // Name string `structs:"myName"` 43 | // 44 | // A tag value with the content of "-" ignores that particular field. Example: 45 | // 46 | // // Field is ignored by this package. 47 | // Field bool `structs:"-"` 48 | // 49 | // A tag value with the content of "string" uses the stringer to get the value. Example: 50 | // 51 | // // The value will be output of Animal's String() func. 52 | // // Map will panic if Animal does not implement String(). 53 | // Field *Animal `structs:"field,string"` 54 | // 55 | // A tag value with the option of "flatten" used in a struct field is to flatten its fields 56 | // in the output map. Example: 57 | // 58 | // // The FieldStruct's fields will be flattened into the output map. 59 | // FieldStruct time.Time `structs:",flatten"` 60 | // 61 | // A tag value with the option of "omitnested" stops iterating further if the type 62 | // is a struct. Example: 63 | // 64 | // // Field is not processed further by this package. 65 | // Field time.Time `structs:"myName,omitnested"` 66 | // Field *http.Request `structs:",omitnested"` 67 | // 68 | // A tag value with the option of "omitempty" ignores that particular field if 69 | // the field value is empty. Example: 70 | // 71 | // // Field appears in map as key "myName", but the field is 72 | // // skipped if empty. 73 | // Field string `structs:"myName,omitempty"` 74 | // 75 | // // Field appears in map as key "Field" (the default), but 76 | // // the field is skipped if empty. 77 | // Field string `structs:",omitempty"` 78 | // 79 | // Note that only exported fields of a struct can be accessed, non exported 80 | // fields will be neglected. 81 | func (s *Struct) Map() map[string]interface{} { 82 | out := make(map[string]interface{}) 83 | s.FillMap(out) 84 | return out 85 | } 86 | 87 | // FillMap is the same as Map. Instead of returning the output, it fills the 88 | // given map. 89 | func (s *Struct) FillMap(out map[string]interface{}) { 90 | if out == nil { 91 | return 92 | } 93 | 94 | fields := s.structFields() 95 | 96 | for _, field := range fields { 97 | name := field.Name 98 | val := s.value.FieldByName(name) 99 | isSubStruct := false 100 | var finalVal interface{} 101 | 102 | tagName, tagOpts := parseTag(field.Tag.Get(s.TagName)) 103 | if tagName != "" { 104 | name = tagName 105 | } 106 | 107 | // if the value is a zero value and the field is marked as omitempty do 108 | // not include 109 | if tagOpts.Has("omitempty") { 110 | zero := reflect.Zero(val.Type()).Interface() 111 | current := val.Interface() 112 | 113 | if reflect.DeepEqual(current, zero) { 114 | continue 115 | } 116 | } 117 | 118 | if !tagOpts.Has("omitnested") { 119 | finalVal = s.nested(val) 120 | 121 | v := reflect.ValueOf(val.Interface()) 122 | if v.Kind() == reflect.Ptr { 123 | v = v.Elem() 124 | } 125 | 126 | switch v.Kind() { 127 | case reflect.Map, reflect.Struct: 128 | isSubStruct = true 129 | } 130 | } else { 131 | finalVal = val.Interface() 132 | } 133 | 134 | if tagOpts.Has("string") { 135 | s, ok := val.Interface().(fmt.Stringer) 136 | if ok { 137 | out[name] = s.String() 138 | } 139 | continue 140 | } 141 | 142 | if isSubStruct && (tagOpts.Has("flatten")) { 143 | for k := range finalVal.(map[string]interface{}) { 144 | out[k] = finalVal.(map[string]interface{})[k] 145 | } 146 | } else { 147 | out[name] = finalVal 148 | } 149 | } 150 | } 151 | 152 | // Values converts the given s struct's field values to a []interface{}. A 153 | // struct tag with the content of "-" ignores the that particular field. 154 | // Example: 155 | // 156 | // // Field is ignored by this package. 157 | // Field int `structs:"-"` 158 | // 159 | // A value with the option of "omitnested" stops iterating further if the type 160 | // is a struct. Example: 161 | // 162 | // // Fields is not processed further by this package. 163 | // Field time.Time `structs:",omitnested"` 164 | // Field *http.Request `structs:",omitnested"` 165 | // 166 | // A tag value with the option of "omitempty" ignores that particular field and 167 | // is not added to the values if the field value is empty. Example: 168 | // 169 | // // Field is skipped if empty 170 | // Field string `structs:",omitempty"` 171 | // 172 | // Note that only exported fields of a struct can be accessed, non exported 173 | // fields will be neglected. 174 | func (s *Struct) Values() []interface{} { 175 | fields := s.structFields() 176 | 177 | var t []interface{} 178 | 179 | for _, field := range fields { 180 | val := s.value.FieldByName(field.Name) 181 | 182 | _, tagOpts := parseTag(field.Tag.Get(s.TagName)) 183 | 184 | // if the value is a zero value and the field is marked as omitempty do 185 | // not include 186 | if tagOpts.Has("omitempty") { 187 | zero := reflect.Zero(val.Type()).Interface() 188 | current := val.Interface() 189 | 190 | if reflect.DeepEqual(current, zero) { 191 | continue 192 | } 193 | } 194 | 195 | if tagOpts.Has("string") { 196 | s, ok := val.Interface().(fmt.Stringer) 197 | if ok { 198 | t = append(t, s.String()) 199 | } 200 | continue 201 | } 202 | 203 | if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { 204 | // look out for embedded structs, and convert them to a 205 | // []interface{} to be added to the final values slice 206 | t = append(t, Values(val.Interface())...) 207 | } else { 208 | t = append(t, val.Interface()) 209 | } 210 | } 211 | 212 | return t 213 | } 214 | 215 | // Fields returns a slice of Fields. A struct tag with the content of "-" 216 | // ignores the checking of that particular field. Example: 217 | // 218 | // // Field is ignored by this package. 219 | // Field bool `structs:"-"` 220 | // 221 | // It panics if s's kind is not struct. 222 | func (s *Struct) Fields() []*Field { 223 | return getFields(s.value, s.TagName) 224 | } 225 | 226 | // Names returns a slice of field names. A struct tag with the content of "-" 227 | // ignores the checking of that particular field. Example: 228 | // 229 | // // Field is ignored by this package. 230 | // Field bool `structs:"-"` 231 | // 232 | // It panics if s's kind is not struct. 233 | func (s *Struct) Names() []string { 234 | fields := getFields(s.value, s.TagName) 235 | 236 | names := make([]string, len(fields)) 237 | 238 | for i, field := range fields { 239 | names[i] = field.Name() 240 | } 241 | 242 | return names 243 | } 244 | 245 | func getFields(v reflect.Value, tagName string) []*Field { 246 | if v.Kind() == reflect.Ptr { 247 | v = v.Elem() 248 | } 249 | 250 | t := v.Type() 251 | 252 | var fields []*Field 253 | 254 | for i := 0; i < t.NumField(); i++ { 255 | field := t.Field(i) 256 | 257 | if tag := field.Tag.Get(tagName); tag == "-" { 258 | continue 259 | } 260 | 261 | f := &Field{ 262 | field: field, 263 | value: v.FieldByName(field.Name), 264 | } 265 | 266 | fields = append(fields, f) 267 | 268 | } 269 | 270 | return fields 271 | } 272 | 273 | // Field returns a new Field struct that provides several high level functions 274 | // around a single struct field entity. It panics if the field is not found. 275 | func (s *Struct) Field(name string) *Field { 276 | f, ok := s.FieldOk(name) 277 | if !ok { 278 | panic("field not found") 279 | } 280 | 281 | return f 282 | } 283 | 284 | // FieldOk returns a new Field struct that provides several high level functions 285 | // around a single struct field entity. The boolean returns true if the field 286 | // was found. 287 | func (s *Struct) FieldOk(name string) (*Field, bool) { 288 | t := s.value.Type() 289 | 290 | field, ok := t.FieldByName(name) 291 | if !ok { 292 | return nil, false 293 | } 294 | 295 | return &Field{ 296 | field: field, 297 | value: s.value.FieldByName(name), 298 | defaultTag: s.TagName, 299 | }, true 300 | } 301 | 302 | // IsZero returns true if all fields in a struct is a zero value (not 303 | // initialized) A struct tag with the content of "-" ignores the checking of 304 | // that particular field. Example: 305 | // 306 | // // Field is ignored by this package. 307 | // Field bool `structs:"-"` 308 | // 309 | // A value with the option of "omitnested" stops iterating further if the type 310 | // is a struct. Example: 311 | // 312 | // // Field is not processed further by this package. 313 | // Field time.Time `structs:"myName,omitnested"` 314 | // Field *http.Request `structs:",omitnested"` 315 | // 316 | // Note that only exported fields of a struct can be accessed, non exported 317 | // fields will be neglected. It panics if s's kind is not struct. 318 | func (s *Struct) IsZero() bool { 319 | fields := s.structFields() 320 | 321 | for _, field := range fields { 322 | val := s.value.FieldByName(field.Name) 323 | 324 | _, tagOpts := parseTag(field.Tag.Get(s.TagName)) 325 | 326 | if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { 327 | ok := IsZero(val.Interface()) 328 | if !ok { 329 | return false 330 | } 331 | 332 | continue 333 | } 334 | 335 | // zero value of the given field, such as "" for string, 0 for int 336 | zero := reflect.Zero(val.Type()).Interface() 337 | 338 | // current value of the given field 339 | current := val.Interface() 340 | 341 | if !reflect.DeepEqual(current, zero) { 342 | return false 343 | } 344 | } 345 | 346 | return true 347 | } 348 | 349 | // HasZero returns true if a field in a struct is not initialized (zero value). 350 | // A struct tag with the content of "-" ignores the checking of that particular 351 | // field. Example: 352 | // 353 | // // Field is ignored by this package. 354 | // Field bool `structs:"-"` 355 | // 356 | // A value with the option of "omitnested" stops iterating further if the type 357 | // is a struct. Example: 358 | // 359 | // // Field is not processed further by this package. 360 | // Field time.Time `structs:"myName,omitnested"` 361 | // Field *http.Request `structs:",omitnested"` 362 | // 363 | // Note that only exported fields of a struct can be accessed, non exported 364 | // fields will be neglected. It panics if s's kind is not struct. 365 | func (s *Struct) HasZero() bool { 366 | fields := s.structFields() 367 | 368 | for _, field := range fields { 369 | val := s.value.FieldByName(field.Name) 370 | 371 | _, tagOpts := parseTag(field.Tag.Get(s.TagName)) 372 | 373 | if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { 374 | ok := HasZero(val.Interface()) 375 | if ok { 376 | return true 377 | } 378 | 379 | continue 380 | } 381 | 382 | // zero value of the given field, such as "" for string, 0 for int 383 | zero := reflect.Zero(val.Type()).Interface() 384 | 385 | // current value of the given field 386 | current := val.Interface() 387 | 388 | if reflect.DeepEqual(current, zero) { 389 | return true 390 | } 391 | } 392 | 393 | return false 394 | } 395 | 396 | // Name returns the structs's type name within its package. For more info refer 397 | // to Name() function. 398 | func (s *Struct) Name() string { 399 | return s.value.Type().Name() 400 | } 401 | 402 | // structFields returns the exported struct fields for a given s struct. This 403 | // is a convenient helper method to avoid duplicate code in some of the 404 | // functions. 405 | func (s *Struct) structFields() []reflect.StructField { 406 | t := s.value.Type() 407 | 408 | var f []reflect.StructField 409 | 410 | for i := 0; i < t.NumField(); i++ { 411 | field := t.Field(i) 412 | // we can't access the value of unexported fields 413 | if field.PkgPath != "" { 414 | continue 415 | } 416 | 417 | // don't check if it's omitted 418 | if tag := field.Tag.Get(s.TagName); tag == "-" { 419 | continue 420 | } 421 | 422 | f = append(f, field) 423 | } 424 | 425 | return f 426 | } 427 | 428 | func strctVal(s interface{}) reflect.Value { 429 | v := reflect.ValueOf(s) 430 | 431 | // if pointer get the underlying element≤ 432 | for v.Kind() == reflect.Ptr { 433 | v = v.Elem() 434 | } 435 | 436 | if v.Kind() != reflect.Struct { 437 | panic("not struct") 438 | } 439 | 440 | return v 441 | } 442 | 443 | // Map converts the given struct to a map[string]interface{}. For more info 444 | // refer to Struct types Map() method. It panics if s's kind is not struct. 445 | func Map(s interface{}) map[string]interface{} { 446 | return New(s).Map() 447 | } 448 | 449 | // FillMap is the same as Map. Instead of returning the output, it fills the 450 | // given map. 451 | func FillMap(s interface{}, out map[string]interface{}) { 452 | New(s).FillMap(out) 453 | } 454 | 455 | // Values converts the given struct to a []interface{}. For more info refer to 456 | // Struct types Values() method. It panics if s's kind is not struct. 457 | func Values(s interface{}) []interface{} { 458 | return New(s).Values() 459 | } 460 | 461 | // Fields returns a slice of *Field. For more info refer to Struct types 462 | // Fields() method. It panics if s's kind is not struct. 463 | func Fields(s interface{}) []*Field { 464 | return New(s).Fields() 465 | } 466 | 467 | // Names returns a slice of field names. For more info refer to Struct types 468 | // Names() method. It panics if s's kind is not struct. 469 | func Names(s interface{}) []string { 470 | return New(s).Names() 471 | } 472 | 473 | // IsZero returns true if all fields is equal to a zero value. For more info 474 | // refer to Struct types IsZero() method. It panics if s's kind is not struct. 475 | func IsZero(s interface{}) bool { 476 | return New(s).IsZero() 477 | } 478 | 479 | // HasZero returns true if any field is equal to a zero value. For more info 480 | // refer to Struct types HasZero() method. It panics if s's kind is not struct. 481 | func HasZero(s interface{}) bool { 482 | return New(s).HasZero() 483 | } 484 | 485 | // IsStruct returns true if the given variable is a struct or a pointer to 486 | // struct. 487 | func IsStruct(s interface{}) bool { 488 | v := reflect.ValueOf(s) 489 | if v.Kind() == reflect.Ptr { 490 | v = v.Elem() 491 | } 492 | 493 | // uninitialized zero value of a struct 494 | if v.Kind() == reflect.Invalid { 495 | return false 496 | } 497 | 498 | return v.Kind() == reflect.Struct 499 | } 500 | 501 | // Name returns the structs's type name within its package. It returns an 502 | // empty string for unnamed types. It panics if s's kind is not struct. 503 | func Name(s interface{}) string { 504 | return New(s).Name() 505 | } 506 | 507 | // nested retrieves recursively all types for the given value and returns the 508 | // nested value. 509 | func (s *Struct) nested(val reflect.Value) interface{} { 510 | var finalVal interface{} 511 | 512 | v := reflect.ValueOf(val.Interface()) 513 | if v.Kind() == reflect.Ptr { 514 | v = v.Elem() 515 | } 516 | 517 | switch v.Kind() { 518 | case reflect.Struct: 519 | n := New(val.Interface()) 520 | n.TagName = s.TagName 521 | m := n.Map() 522 | 523 | // do not add the converted value if there are no exported fields, ie: 524 | // time.Time 525 | if len(m) == 0 { 526 | finalVal = val.Interface() 527 | } else { 528 | finalVal = m 529 | } 530 | case reflect.Map: 531 | // get the element type of the map 532 | mapElem := val.Type() 533 | switch val.Type().Kind() { 534 | case reflect.Ptr, reflect.Array, reflect.Map, 535 | reflect.Slice, reflect.Chan: 536 | mapElem = val.Type().Elem() 537 | if mapElem.Kind() == reflect.Ptr { 538 | mapElem = mapElem.Elem() 539 | } 540 | } 541 | 542 | // only iterate over struct types, ie: map[string]StructType, 543 | // map[string][]StructType, 544 | if mapElem.Kind() == reflect.Struct || 545 | (mapElem.Kind() == reflect.Slice && 546 | mapElem.Elem().Kind() == reflect.Struct) { 547 | m := make(map[string]interface{}, val.Len()) 548 | for _, k := range val.MapKeys() { 549 | m[k.String()] = s.nested(val.MapIndex(k)) 550 | } 551 | finalVal = m 552 | break 553 | } 554 | 555 | // TODO(arslan): should this be optional? 556 | finalVal = val.Interface() 557 | case reflect.Slice, reflect.Array: 558 | if val.Type().Kind() == reflect.Interface { 559 | finalVal = val.Interface() 560 | break 561 | } 562 | 563 | // TODO(arslan): should this be optional? 564 | // do not iterate of non struct types, just pass the value. Ie: []int, 565 | // []string, co... We only iterate further if it's a struct. 566 | // i.e []foo or []*foo 567 | if val.Type().Elem().Kind() != reflect.Struct && 568 | !(val.Type().Elem().Kind() == reflect.Ptr && 569 | val.Type().Elem().Elem().Kind() == reflect.Struct) { 570 | finalVal = val.Interface() 571 | break 572 | } 573 | 574 | slices := make([]interface{}, val.Len()) 575 | for x := 0; x < val.Len(); x++ { 576 | slices[x] = s.nested(val.Index(x)) 577 | } 578 | finalVal = slices 579 | default: 580 | finalVal = val.Interface() 581 | } 582 | 583 | return finalVal 584 | } 585 | -------------------------------------------------------------------------------- /structs_example_test.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func ExampleNew() { 9 | type Server struct { 10 | Name string 11 | ID int32 12 | Enabled bool 13 | } 14 | 15 | server := &Server{ 16 | Name: "Arslan", 17 | ID: 123456, 18 | Enabled: true, 19 | } 20 | 21 | s := New(server) 22 | 23 | fmt.Printf("Name : %v\n", s.Name()) 24 | fmt.Printf("Values : %v\n", s.Values()) 25 | fmt.Printf("Value of ID : %v\n", s.Field("ID").Value()) 26 | // Output: 27 | // Name : Server 28 | // Values : [Arslan 123456 true] 29 | // Value of ID : 123456 30 | 31 | } 32 | 33 | func ExampleMap() { 34 | type Server struct { 35 | Name string 36 | ID int32 37 | Enabled bool 38 | } 39 | 40 | s := &Server{ 41 | Name: "Arslan", 42 | ID: 123456, 43 | Enabled: true, 44 | } 45 | 46 | m := Map(s) 47 | 48 | fmt.Printf("%#v\n", m["Name"]) 49 | fmt.Printf("%#v\n", m["ID"]) 50 | fmt.Printf("%#v\n", m["Enabled"]) 51 | // Output: 52 | // "Arslan" 53 | // 123456 54 | // true 55 | 56 | } 57 | 58 | func ExampleMap_tags() { 59 | // Custom tags can change the map keys instead of using the fields name 60 | type Server struct { 61 | Name string `structs:"server_name"` 62 | ID int32 `structs:"server_id"` 63 | Enabled bool `structs:"enabled"` 64 | } 65 | 66 | s := &Server{ 67 | Name: "Zeynep", 68 | ID: 789012, 69 | } 70 | 71 | m := Map(s) 72 | 73 | // access them by the custom tags defined above 74 | fmt.Printf("%#v\n", m["server_name"]) 75 | fmt.Printf("%#v\n", m["server_id"]) 76 | fmt.Printf("%#v\n", m["enabled"]) 77 | // Output: 78 | // "Zeynep" 79 | // 789012 80 | // false 81 | 82 | } 83 | 84 | func ExampleMap_omitNested() { 85 | // By default field with struct types are processed too. We can stop 86 | // processing them via "omitnested" tag option. 87 | type Server struct { 88 | Name string `structs:"server_name"` 89 | ID int32 `structs:"server_id"` 90 | Time time.Time `structs:"time,omitnested"` // do not convert to map[string]interface{} 91 | } 92 | 93 | const shortForm = "2006-Jan-02" 94 | t, _ := time.Parse("2006-Jan-02", "2013-Feb-03") 95 | 96 | s := &Server{ 97 | Name: "Zeynep", 98 | ID: 789012, 99 | Time: t, 100 | } 101 | 102 | m := Map(s) 103 | 104 | // access them by the custom tags defined above 105 | fmt.Printf("%v\n", m["server_name"]) 106 | fmt.Printf("%v\n", m["server_id"]) 107 | fmt.Printf("%v\n", m["time"].(time.Time)) 108 | // Output: 109 | // Zeynep 110 | // 789012 111 | // 2013-02-03 00:00:00 +0000 UTC 112 | } 113 | 114 | func ExampleMap_omitEmpty() { 115 | // By default field with struct types of zero values are processed too. We 116 | // can stop processing them via "omitempty" tag option. 117 | type Server struct { 118 | Name string `structs:",omitempty"` 119 | ID int32 `structs:"server_id,omitempty"` 120 | Location string 121 | } 122 | 123 | // Only add location 124 | s := &Server{ 125 | Location: "Tokyo", 126 | } 127 | 128 | m := Map(s) 129 | 130 | // map contains only the Location field 131 | fmt.Printf("%v\n", m) 132 | // Output: 133 | // map[Location:Tokyo] 134 | } 135 | 136 | func ExampleValues() { 137 | type Server struct { 138 | Name string 139 | ID int32 140 | Enabled bool 141 | } 142 | 143 | s := &Server{ 144 | Name: "Fatih", 145 | ID: 135790, 146 | Enabled: false, 147 | } 148 | 149 | m := Values(s) 150 | 151 | fmt.Printf("Values: %+v\n", m) 152 | // Output: 153 | // Values: [Fatih 135790 false] 154 | } 155 | 156 | func ExampleValues_omitEmpty() { 157 | // By default field with struct types of zero values are processed too. We 158 | // can stop processing them via "omitempty" tag option. 159 | type Server struct { 160 | Name string `structs:",omitempty"` 161 | ID int32 `structs:"server_id,omitempty"` 162 | Location string 163 | } 164 | 165 | // Only add location 166 | s := &Server{ 167 | Location: "Ankara", 168 | } 169 | 170 | m := Values(s) 171 | 172 | // values contains only the Location field 173 | fmt.Printf("Values: %+v\n", m) 174 | // Output: 175 | // Values: [Ankara] 176 | } 177 | 178 | func ExampleValues_tags() { 179 | type Location struct { 180 | City string 181 | Country string 182 | } 183 | 184 | type Server struct { 185 | Name string 186 | ID int32 187 | Enabled bool 188 | Location Location `structs:"-"` // values from location are not included anymore 189 | } 190 | 191 | s := &Server{ 192 | Name: "Fatih", 193 | ID: 135790, 194 | Enabled: false, 195 | Location: Location{City: "Ankara", Country: "Turkey"}, 196 | } 197 | 198 | // Let get all values from the struct s. Note that we don't include values 199 | // from the Location field 200 | m := Values(s) 201 | 202 | fmt.Printf("Values: %+v\n", m) 203 | // Output: 204 | // Values: [Fatih 135790 false] 205 | } 206 | 207 | func ExampleFields() { 208 | type Access struct { 209 | Name string 210 | LastAccessed time.Time 211 | Number int 212 | } 213 | 214 | s := &Access{ 215 | Name: "Fatih", 216 | LastAccessed: time.Now(), 217 | Number: 1234567, 218 | } 219 | 220 | fields := Fields(s) 221 | 222 | for i, field := range fields { 223 | fmt.Printf("[%d] %+v\n", i, field.Name()) 224 | } 225 | 226 | // Output: 227 | // [0] Name 228 | // [1] LastAccessed 229 | // [2] Number 230 | } 231 | 232 | func ExampleFields_nested() { 233 | type Person struct { 234 | Name string 235 | Number int 236 | } 237 | 238 | type Access struct { 239 | Person Person 240 | HasPermission bool 241 | LastAccessed time.Time 242 | } 243 | 244 | s := &Access{ 245 | Person: Person{Name: "fatih", Number: 1234567}, 246 | LastAccessed: time.Now(), 247 | HasPermission: true, 248 | } 249 | 250 | // Let's get all fields from the struct s. 251 | fields := Fields(s) 252 | 253 | for _, field := range fields { 254 | if field.Name() == "Person" { 255 | fmt.Printf("Access.Person.Name: %+v\n", field.Field("Name").Value()) 256 | } 257 | } 258 | 259 | // Output: 260 | // Access.Person.Name: fatih 261 | } 262 | 263 | func ExampleField() { 264 | type Person struct { 265 | Name string 266 | Number int 267 | } 268 | 269 | type Access struct { 270 | Person Person 271 | HasPermission bool 272 | LastAccessed time.Time 273 | } 274 | 275 | access := &Access{ 276 | Person: Person{Name: "fatih", Number: 1234567}, 277 | LastAccessed: time.Now(), 278 | HasPermission: true, 279 | } 280 | 281 | // Create a new Struct type 282 | s := New(access) 283 | 284 | // Get the Field type for "Person" field 285 | p := s.Field("Person") 286 | 287 | // Get the underlying "Name field" and print the value of it 288 | name := p.Field("Name") 289 | 290 | fmt.Printf("Value of Person.Access.Name: %+v\n", name.Value()) 291 | 292 | // Output: 293 | // Value of Person.Access.Name: fatih 294 | 295 | } 296 | 297 | func ExampleIsZero() { 298 | type Server struct { 299 | Name string 300 | ID int32 301 | Enabled bool 302 | } 303 | 304 | // Nothing is initialized 305 | a := &Server{} 306 | isZeroA := IsZero(a) 307 | 308 | // Name and Enabled is initialized, but not ID 309 | b := &Server{ 310 | Name: "Golang", 311 | Enabled: true, 312 | } 313 | isZeroB := IsZero(b) 314 | 315 | fmt.Printf("%#v\n", isZeroA) 316 | fmt.Printf("%#v\n", isZeroB) 317 | // Output: 318 | // true 319 | // false 320 | } 321 | 322 | func ExampleHasZero() { 323 | // Let's define an Access struct. Note that the "Enabled" field is not 324 | // going to be checked because we added the "structs" tag to the field. 325 | type Access struct { 326 | Name string 327 | LastAccessed time.Time 328 | Number int 329 | Enabled bool `structs:"-"` 330 | } 331 | 332 | // Name and Number is not initialized. 333 | a := &Access{ 334 | LastAccessed: time.Now(), 335 | } 336 | hasZeroA := HasZero(a) 337 | 338 | // Name and Number is initialized. 339 | b := &Access{ 340 | Name: "Fatih", 341 | LastAccessed: time.Now(), 342 | Number: 12345, 343 | } 344 | hasZeroB := HasZero(b) 345 | 346 | fmt.Printf("%#v\n", hasZeroA) 347 | fmt.Printf("%#v\n", hasZeroB) 348 | // Output: 349 | // true 350 | // false 351 | } 352 | -------------------------------------------------------------------------------- /structs_test.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestMapNonStruct(t *testing.T) { 11 | foo := []string{"foo"} 12 | 13 | defer func() { 14 | err := recover() 15 | if err == nil { 16 | t.Error("Passing a non struct into Map should panic") 17 | } 18 | }() 19 | 20 | // this should panic. We are going to recover and and test it 21 | _ = Map(foo) 22 | } 23 | 24 | func TestStructIndexes(t *testing.T) { 25 | type C struct { 26 | something int 27 | Props map[string]interface{} 28 | } 29 | 30 | defer func() { 31 | err := recover() 32 | if err != nil { 33 | fmt.Printf("err %+v\n", err) 34 | t.Error("Using mixed indexes should not panic") 35 | } 36 | }() 37 | 38 | // They should not panic 39 | _ = Map(&C{}) 40 | _ = Fields(&C{}) 41 | _ = Values(&C{}) 42 | _ = IsZero(&C{}) 43 | _ = HasZero(&C{}) 44 | } 45 | 46 | func TestMap(t *testing.T) { 47 | var T = struct { 48 | A string 49 | B int 50 | C bool 51 | }{ 52 | A: "a-value", 53 | B: 2, 54 | C: true, 55 | } 56 | 57 | a := Map(T) 58 | 59 | if typ := reflect.TypeOf(a).Kind(); typ != reflect.Map { 60 | t.Errorf("Map should return a map type, got: %v", typ) 61 | } 62 | 63 | // we have three fields 64 | if len(a) != 3 { 65 | t.Errorf("Map should return a map of len 3, got: %d", len(a)) 66 | } 67 | 68 | inMap := func(val interface{}) bool { 69 | for _, v := range a { 70 | if reflect.DeepEqual(v, val) { 71 | return true 72 | } 73 | } 74 | 75 | return false 76 | } 77 | 78 | for _, val := range []interface{}{"a-value", 2, true} { 79 | if !inMap(val) { 80 | t.Errorf("Map should have the value %v", val) 81 | } 82 | } 83 | 84 | } 85 | 86 | func TestMap_Tag(t *testing.T) { 87 | var T = struct { 88 | A string `structs:"x"` 89 | B int `structs:"y"` 90 | C bool `structs:"z"` 91 | }{ 92 | A: "a-value", 93 | B: 2, 94 | C: true, 95 | } 96 | 97 | a := Map(T) 98 | 99 | inMap := func(key interface{}) bool { 100 | for k := range a { 101 | if reflect.DeepEqual(k, key) { 102 | return true 103 | } 104 | } 105 | return false 106 | } 107 | 108 | for _, key := range []string{"x", "y", "z"} { 109 | if !inMap(key) { 110 | t.Errorf("Map should have the key %v", key) 111 | } 112 | } 113 | 114 | } 115 | 116 | func TestMap_CustomTag(t *testing.T) { 117 | var T = struct { 118 | A string `json:"x"` 119 | B int `json:"y"` 120 | C bool `json:"z"` 121 | D struct { 122 | E string `json:"jkl"` 123 | } `json:"nested"` 124 | }{ 125 | A: "a-value", 126 | B: 2, 127 | C: true, 128 | } 129 | T.D.E = "e-value" 130 | 131 | s := New(T) 132 | s.TagName = "json" 133 | 134 | a := s.Map() 135 | 136 | inMap := func(key interface{}) bool { 137 | for k := range a { 138 | if reflect.DeepEqual(k, key) { 139 | return true 140 | } 141 | } 142 | return false 143 | } 144 | 145 | for _, key := range []string{"x", "y", "z"} { 146 | if !inMap(key) { 147 | t.Errorf("Map should have the key %v", key) 148 | } 149 | } 150 | 151 | nested, ok := a["nested"].(map[string]interface{}) 152 | if !ok { 153 | t.Fatalf("Map should contain the D field that is tagged as 'nested'") 154 | } 155 | 156 | e, ok := nested["jkl"].(string) 157 | if !ok { 158 | t.Fatalf("Map should contain the D.E field that is tagged as 'jkl'") 159 | } 160 | 161 | if e != "e-value" { 162 | t.Errorf("D.E field should be equal to 'e-value', got: '%v'", e) 163 | } 164 | 165 | } 166 | 167 | func TestMap_MultipleCustomTag(t *testing.T) { 168 | var A = struct { 169 | X string `aa:"ax"` 170 | }{"a_value"} 171 | 172 | aStruct := New(A) 173 | aStruct.TagName = "aa" 174 | 175 | var B = struct { 176 | X string `bb:"bx"` 177 | }{"b_value"} 178 | 179 | bStruct := New(B) 180 | bStruct.TagName = "bb" 181 | 182 | a, b := aStruct.Map(), bStruct.Map() 183 | if !reflect.DeepEqual(a, map[string]interface{}{"ax": "a_value"}) { 184 | t.Error("Map should have field ax with value a_value") 185 | } 186 | 187 | if !reflect.DeepEqual(b, map[string]interface{}{"bx": "b_value"}) { 188 | t.Error("Map should have field bx with value b_value") 189 | } 190 | } 191 | 192 | func TestMap_OmitEmpty(t *testing.T) { 193 | type A struct { 194 | Name string 195 | Value string `structs:",omitempty"` 196 | Time time.Time `structs:",omitempty"` 197 | } 198 | a := A{} 199 | 200 | m := Map(a) 201 | 202 | _, ok := m["Value"].(map[string]interface{}) 203 | if ok { 204 | t.Error("Map should not contain the Value field that is tagged as omitempty") 205 | } 206 | 207 | _, ok = m["Time"].(map[string]interface{}) 208 | if ok { 209 | t.Error("Map should not contain the Time field that is tagged as omitempty") 210 | } 211 | } 212 | 213 | func TestMap_OmitNested(t *testing.T) { 214 | type A struct { 215 | Name string 216 | Value string 217 | Time time.Time `structs:",omitnested"` 218 | } 219 | a := A{Time: time.Now()} 220 | 221 | type B struct { 222 | Desc string 223 | A A 224 | } 225 | b := &B{A: a} 226 | 227 | m := Map(b) 228 | 229 | in, ok := m["A"].(map[string]interface{}) 230 | if !ok { 231 | t.Error("Map nested structs is not available in the map") 232 | } 233 | 234 | // should not happen 235 | if _, ok := in["Time"].(map[string]interface{}); ok { 236 | t.Error("Map nested struct should omit recursiving parsing of Time") 237 | } 238 | 239 | if _, ok := in["Time"].(time.Time); !ok { 240 | t.Error("Map nested struct should stop parsing of Time at is current value") 241 | } 242 | } 243 | 244 | func TestMap_Nested(t *testing.T) { 245 | type A struct { 246 | Name string 247 | } 248 | a := &A{Name: "example"} 249 | 250 | type B struct { 251 | A *A 252 | } 253 | b := &B{A: a} 254 | 255 | m := Map(b) 256 | 257 | if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { 258 | t.Errorf("Map should return a map type, got: %v", typ) 259 | } 260 | 261 | in, ok := m["A"].(map[string]interface{}) 262 | if !ok { 263 | t.Error("Map nested structs is not available in the map") 264 | } 265 | 266 | if name := in["Name"].(string); name != "example" { 267 | t.Errorf("Map nested struct's name field should give example, got: %s", name) 268 | } 269 | } 270 | 271 | func TestMap_NestedMapWithStructValues(t *testing.T) { 272 | type A struct { 273 | Name string 274 | } 275 | 276 | type B struct { 277 | A map[string]*A 278 | } 279 | 280 | a := &A{Name: "example"} 281 | 282 | b := &B{ 283 | A: map[string]*A{ 284 | "example_key": a, 285 | }, 286 | } 287 | 288 | m := Map(b) 289 | 290 | if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { 291 | t.Errorf("Map should return a map type, got: %v", typ) 292 | } 293 | 294 | in, ok := m["A"].(map[string]interface{}) 295 | if !ok { 296 | t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["A"]) 297 | } 298 | 299 | example := in["example_key"].(map[string]interface{}) 300 | if name := example["Name"].(string); name != "example" { 301 | t.Errorf("Map nested struct's name field should give example, got: %s", name) 302 | } 303 | } 304 | 305 | func TestMap_NestedMapWithStringValues(t *testing.T) { 306 | type B struct { 307 | Foo map[string]string 308 | } 309 | 310 | type A struct { 311 | B *B 312 | } 313 | 314 | b := &B{ 315 | Foo: map[string]string{ 316 | "example_key": "example", 317 | }, 318 | } 319 | 320 | a := &A{B: b} 321 | 322 | m := Map(a) 323 | 324 | if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { 325 | t.Errorf("Map should return a map type, got: %v", typ) 326 | } 327 | 328 | in, ok := m["B"].(map[string]interface{}) 329 | if !ok { 330 | t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"]) 331 | } 332 | 333 | foo := in["Foo"].(map[string]string) 334 | if name := foo["example_key"]; name != "example" { 335 | t.Errorf("Map nested struct's name field should give example, got: %s", name) 336 | } 337 | } 338 | func TestMap_NestedMapWithInterfaceValues(t *testing.T) { 339 | type B struct { 340 | Foo map[string]interface{} 341 | } 342 | 343 | type A struct { 344 | B *B 345 | } 346 | 347 | b := &B{ 348 | Foo: map[string]interface{}{ 349 | "example_key": "example", 350 | }, 351 | } 352 | 353 | a := &A{B: b} 354 | 355 | m := Map(a) 356 | 357 | if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { 358 | t.Errorf("Map should return a map type, got: %v", typ) 359 | } 360 | 361 | in, ok := m["B"].(map[string]interface{}) 362 | if !ok { 363 | t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"]) 364 | } 365 | 366 | foo := in["Foo"].(map[string]interface{}) 367 | if name := foo["example_key"]; name != "example" { 368 | t.Errorf("Map nested struct's name field should give example, got: %s", name) 369 | } 370 | } 371 | 372 | func TestMap_NestedMapWithSliceIntValues(t *testing.T) { 373 | type B struct { 374 | Foo map[string][]int 375 | } 376 | 377 | type A struct { 378 | B *B 379 | } 380 | 381 | b := &B{ 382 | Foo: map[string][]int{ 383 | "example_key": {80}, 384 | }, 385 | } 386 | 387 | a := &A{B: b} 388 | 389 | m := Map(a) 390 | 391 | if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { 392 | t.Errorf("Map should return a map type, got: %v", typ) 393 | } 394 | 395 | in, ok := m["B"].(map[string]interface{}) 396 | if !ok { 397 | t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"]) 398 | } 399 | 400 | foo := in["Foo"].(map[string][]int) 401 | if name := foo["example_key"]; name[0] != 80 { 402 | t.Errorf("Map nested struct's name field should give example, got: %v", name) 403 | } 404 | } 405 | 406 | func TestMap_NestedMapWithSliceStructValues(t *testing.T) { 407 | type address struct { 408 | Country string `structs:"country"` 409 | } 410 | 411 | type B struct { 412 | Foo map[string][]address 413 | } 414 | 415 | type A struct { 416 | B *B 417 | } 418 | 419 | b := &B{ 420 | Foo: map[string][]address{ 421 | "example_key": { 422 | {Country: "Turkey"}, 423 | }, 424 | }, 425 | } 426 | 427 | a := &A{B: b} 428 | m := Map(a) 429 | 430 | if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { 431 | t.Errorf("Map should return a map type, got: %v", typ) 432 | } 433 | 434 | in, ok := m["B"].(map[string]interface{}) 435 | if !ok { 436 | t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"]) 437 | } 438 | 439 | foo := in["Foo"].(map[string]interface{}) 440 | 441 | addresses := foo["example_key"].([]interface{}) 442 | 443 | addr, ok := addresses[0].(map[string]interface{}) 444 | if !ok { 445 | t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"]) 446 | } 447 | 448 | if _, exists := addr["country"]; !exists { 449 | t.Errorf("Expecting country, but found Country") 450 | } 451 | } 452 | 453 | func TestMap_NestedSliceWithStructValues(t *testing.T) { 454 | type address struct { 455 | Country string `structs:"customCountryName"` 456 | } 457 | 458 | type person struct { 459 | Name string `structs:"name"` 460 | Addresses []address `structs:"addresses"` 461 | } 462 | 463 | p := person{ 464 | Name: "test", 465 | Addresses: []address{ 466 | {Country: "England"}, 467 | {Country: "Italy"}, 468 | }, 469 | } 470 | mp := Map(p) 471 | 472 | mpAddresses := mp["addresses"].([]interface{}) 473 | if _, exists := mpAddresses[0].(map[string]interface{})["Country"]; exists { 474 | t.Errorf("Expecting customCountryName, but found Country") 475 | } 476 | 477 | if _, exists := mpAddresses[0].(map[string]interface{})["customCountryName"]; !exists { 478 | t.Errorf("customCountryName key not found") 479 | } 480 | } 481 | 482 | func TestMap_NestedSliceWithPointerOfStructValues(t *testing.T) { 483 | type address struct { 484 | Country string `structs:"customCountryName"` 485 | } 486 | 487 | type person struct { 488 | Name string `structs:"name"` 489 | Addresses []*address `structs:"addresses"` 490 | } 491 | 492 | p := person{ 493 | Name: "test", 494 | Addresses: []*address{ 495 | {Country: "England"}, 496 | {Country: "Italy"}, 497 | }, 498 | } 499 | mp := Map(p) 500 | 501 | mpAddresses := mp["addresses"].([]interface{}) 502 | if _, exists := mpAddresses[0].(map[string]interface{})["Country"]; exists { 503 | t.Errorf("Expecting customCountryName, but found Country") 504 | } 505 | 506 | if _, exists := mpAddresses[0].(map[string]interface{})["customCountryName"]; !exists { 507 | t.Errorf("customCountryName key not found") 508 | } 509 | } 510 | 511 | func TestMap_NestedSliceWithIntValues(t *testing.T) { 512 | type person struct { 513 | Name string `structs:"name"` 514 | Ports []int `structs:"ports"` 515 | } 516 | 517 | p := person{ 518 | Name: "test", 519 | Ports: []int{80}, 520 | } 521 | m := Map(p) 522 | 523 | ports, ok := m["ports"].([]int) 524 | if !ok { 525 | t.Errorf("Nested type of map should be of type []int, have %T", m["ports"]) 526 | } 527 | 528 | if ports[0] != 80 { 529 | t.Errorf("Map nested struct's ports field should give 80, got: %v", ports) 530 | } 531 | } 532 | 533 | func TestMap_Anonymous(t *testing.T) { 534 | type A struct { 535 | Name string 536 | } 537 | a := &A{Name: "example"} 538 | 539 | type B struct { 540 | *A 541 | } 542 | b := &B{} 543 | b.A = a 544 | 545 | m := Map(b) 546 | 547 | if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { 548 | t.Errorf("Map should return a map type, got: %v", typ) 549 | } 550 | 551 | in, ok := m["A"].(map[string]interface{}) 552 | if !ok { 553 | t.Error("Embedded structs is not available in the map") 554 | } 555 | 556 | if name := in["Name"].(string); name != "example" { 557 | t.Errorf("Embedded A struct's Name field should give example, got: %s", name) 558 | } 559 | } 560 | 561 | func TestMap_Flatnested(t *testing.T) { 562 | type A struct { 563 | Name string 564 | } 565 | a := A{Name: "example"} 566 | 567 | type B struct { 568 | A `structs:",flatten"` 569 | C int 570 | } 571 | b := &B{C: 123} 572 | b.A = a 573 | 574 | m := Map(b) 575 | 576 | _, ok := m["A"].(map[string]interface{}) 577 | if ok { 578 | t.Error("Embedded A struct with tag flatten has to be flat in the map") 579 | } 580 | 581 | expectedMap := map[string]interface{}{"Name": "example", "C": 123} 582 | if !reflect.DeepEqual(m, expectedMap) { 583 | t.Errorf("The exprected map %+v does't correspond to %+v", expectedMap, m) 584 | } 585 | 586 | } 587 | 588 | func TestMap_FlatnestedOverwrite(t *testing.T) { 589 | type A struct { 590 | Name string 591 | } 592 | a := A{Name: "example"} 593 | 594 | type B struct { 595 | A `structs:",flatten"` 596 | Name string 597 | C int 598 | } 599 | b := &B{C: 123, Name: "bName"} 600 | b.A = a 601 | 602 | m := Map(b) 603 | 604 | _, ok := m["A"].(map[string]interface{}) 605 | if ok { 606 | t.Error("Embedded A struct with tag flatten has to be flat in the map") 607 | } 608 | 609 | expectedMap := map[string]interface{}{"Name": "bName", "C": 123} 610 | if !reflect.DeepEqual(m, expectedMap) { 611 | t.Errorf("The exprected map %+v does't correspond to %+v", expectedMap, m) 612 | } 613 | } 614 | 615 | func TestMap_TimeField(t *testing.T) { 616 | type A struct { 617 | CreatedAt time.Time 618 | } 619 | 620 | a := &A{CreatedAt: time.Now().UTC()} 621 | m := Map(a) 622 | 623 | _, ok := m["CreatedAt"].(time.Time) 624 | if !ok { 625 | t.Error("Time field must be final") 626 | } 627 | } 628 | 629 | func TestFillMap(t *testing.T) { 630 | var T = struct { 631 | A string 632 | B int 633 | C bool 634 | }{ 635 | A: "a-value", 636 | B: 2, 637 | C: true, 638 | } 639 | 640 | a := make(map[string]interface{}, 0) 641 | FillMap(T, a) 642 | 643 | // we have three fields 644 | if len(a) != 3 { 645 | t.Errorf("FillMap should fill a map of len 3, got: %d", len(a)) 646 | } 647 | 648 | inMap := func(val interface{}) bool { 649 | for _, v := range a { 650 | if reflect.DeepEqual(v, val) { 651 | return true 652 | } 653 | } 654 | 655 | return false 656 | } 657 | 658 | for _, val := range []interface{}{"a-value", 2, true} { 659 | if !inMap(val) { 660 | t.Errorf("FillMap should have the value %v", val) 661 | } 662 | } 663 | } 664 | 665 | func TestFillMap_Nil(t *testing.T) { 666 | var T = struct { 667 | A string 668 | B int 669 | C bool 670 | }{ 671 | A: "a-value", 672 | B: 2, 673 | C: true, 674 | } 675 | 676 | defer func() { 677 | err := recover() 678 | if err != nil { 679 | t.Error("FillMap should not panic if a nil map is passed") 680 | } 681 | }() 682 | 683 | // nil should no 684 | FillMap(T, nil) 685 | } 686 | func TestStruct(t *testing.T) { 687 | var T = struct{}{} 688 | 689 | if !IsStruct(T) { 690 | t.Errorf("T should be a struct, got: %T", T) 691 | } 692 | 693 | if !IsStruct(&T) { 694 | t.Errorf("T should be a struct, got: %T", T) 695 | } 696 | 697 | } 698 | 699 | func TestValues(t *testing.T) { 700 | var T = struct { 701 | A string 702 | B int 703 | C bool 704 | }{ 705 | A: "a-value", 706 | B: 2, 707 | C: true, 708 | } 709 | 710 | s := Values(T) 711 | 712 | if typ := reflect.TypeOf(s).Kind(); typ != reflect.Slice { 713 | t.Errorf("Values should return a slice type, got: %v", typ) 714 | } 715 | 716 | inSlice := func(val interface{}) bool { 717 | for _, v := range s { 718 | if reflect.DeepEqual(v, val) { 719 | return true 720 | } 721 | } 722 | return false 723 | } 724 | 725 | for _, val := range []interface{}{"a-value", 2, true} { 726 | if !inSlice(val) { 727 | t.Errorf("Values should have the value %v", val) 728 | } 729 | } 730 | } 731 | 732 | func TestValues_OmitEmpty(t *testing.T) { 733 | type A struct { 734 | Name string 735 | Value int `structs:",omitempty"` 736 | } 737 | 738 | a := A{Name: "example"} 739 | s := Values(a) 740 | 741 | if len(s) != 1 { 742 | t.Errorf("Values of omitted empty fields should be not counted") 743 | } 744 | 745 | if s[0].(string) != "example" { 746 | t.Errorf("Values of omitted empty fields should left the value example") 747 | } 748 | } 749 | 750 | func TestValues_OmitNested(t *testing.T) { 751 | type A struct { 752 | Name string 753 | Value int 754 | } 755 | 756 | a := A{ 757 | Name: "example", 758 | Value: 123, 759 | } 760 | 761 | type B struct { 762 | A A `structs:",omitnested"` 763 | C int 764 | } 765 | b := &B{A: a, C: 123} 766 | 767 | s := Values(b) 768 | 769 | if len(s) != 2 { 770 | t.Errorf("Values of omitted nested struct should be not counted") 771 | } 772 | 773 | inSlice := func(val interface{}) bool { 774 | for _, v := range s { 775 | if reflect.DeepEqual(v, val) { 776 | return true 777 | } 778 | } 779 | return false 780 | } 781 | 782 | for _, val := range []interface{}{123, a} { 783 | if !inSlice(val) { 784 | t.Errorf("Values should have the value %v", val) 785 | } 786 | } 787 | } 788 | 789 | func TestValues_Nested(t *testing.T) { 790 | type A struct { 791 | Name string 792 | } 793 | a := A{Name: "example"} 794 | 795 | type B struct { 796 | A A 797 | C int 798 | } 799 | b := &B{A: a, C: 123} 800 | 801 | s := Values(b) 802 | 803 | inSlice := func(val interface{}) bool { 804 | for _, v := range s { 805 | if reflect.DeepEqual(v, val) { 806 | return true 807 | } 808 | } 809 | return false 810 | } 811 | 812 | for _, val := range []interface{}{"example", 123} { 813 | if !inSlice(val) { 814 | t.Errorf("Values should have the value %v", val) 815 | } 816 | } 817 | } 818 | 819 | func TestValues_Anonymous(t *testing.T) { 820 | type A struct { 821 | Name string 822 | } 823 | a := A{Name: "example"} 824 | 825 | type B struct { 826 | A 827 | C int 828 | } 829 | b := &B{C: 123} 830 | b.A = a 831 | 832 | s := Values(b) 833 | 834 | inSlice := func(val interface{}) bool { 835 | for _, v := range s { 836 | if reflect.DeepEqual(v, val) { 837 | return true 838 | } 839 | } 840 | return false 841 | } 842 | 843 | for _, val := range []interface{}{"example", 123} { 844 | if !inSlice(val) { 845 | t.Errorf("Values should have the value %v", val) 846 | } 847 | } 848 | } 849 | 850 | func TestNames(t *testing.T) { 851 | var T = struct { 852 | A string 853 | B int 854 | C bool 855 | }{ 856 | A: "a-value", 857 | B: 2, 858 | C: true, 859 | } 860 | 861 | s := Names(T) 862 | 863 | if len(s) != 3 { 864 | t.Errorf("Names should return a slice of len 3, got: %d", len(s)) 865 | } 866 | 867 | inSlice := func(val string) bool { 868 | for _, v := range s { 869 | if reflect.DeepEqual(v, val) { 870 | return true 871 | } 872 | } 873 | return false 874 | } 875 | 876 | for _, val := range []string{"A", "B", "C"} { 877 | if !inSlice(val) { 878 | t.Errorf("Names should have the value %v", val) 879 | } 880 | } 881 | } 882 | 883 | func TestFields(t *testing.T) { 884 | var T = struct { 885 | A string 886 | B int 887 | C bool 888 | }{ 889 | A: "a-value", 890 | B: 2, 891 | C: true, 892 | } 893 | 894 | s := Fields(T) 895 | 896 | if len(s) != 3 { 897 | t.Errorf("Fields should return a slice of len 3, got: %d", len(s)) 898 | } 899 | 900 | inSlice := func(val string) bool { 901 | for _, v := range s { 902 | if reflect.DeepEqual(v.Name(), val) { 903 | return true 904 | } 905 | } 906 | return false 907 | } 908 | 909 | for _, val := range []string{"A", "B", "C"} { 910 | if !inSlice(val) { 911 | t.Errorf("Fields should have the value %v", val) 912 | } 913 | } 914 | } 915 | 916 | func TestFields_OmitNested(t *testing.T) { 917 | type A struct { 918 | Name string 919 | Enabled bool 920 | } 921 | a := A{Name: "example"} 922 | 923 | type B struct { 924 | A A 925 | C int 926 | Value string `structs:"-"` 927 | Number int 928 | } 929 | b := &B{A: a, C: 123} 930 | 931 | s := Fields(b) 932 | 933 | if len(s) != 3 { 934 | t.Errorf("Fields should omit nested struct. Expecting 2 got: %d", len(s)) 935 | } 936 | 937 | inSlice := func(val interface{}) bool { 938 | for _, v := range s { 939 | if reflect.DeepEqual(v.Name(), val) { 940 | return true 941 | } 942 | } 943 | return false 944 | } 945 | 946 | for _, val := range []interface{}{"A", "C"} { 947 | if !inSlice(val) { 948 | t.Errorf("Fields should have the value %v", val) 949 | } 950 | } 951 | } 952 | 953 | func TestFields_Anonymous(t *testing.T) { 954 | type A struct { 955 | Name string 956 | } 957 | a := A{Name: "example"} 958 | 959 | type B struct { 960 | A 961 | C int 962 | } 963 | b := &B{C: 123} 964 | b.A = a 965 | 966 | s := Fields(b) 967 | 968 | inSlice := func(val interface{}) bool { 969 | for _, v := range s { 970 | if reflect.DeepEqual(v.Name(), val) { 971 | return true 972 | } 973 | } 974 | return false 975 | } 976 | 977 | for _, val := range []interface{}{"A", "C"} { 978 | if !inSlice(val) { 979 | t.Errorf("Fields should have the value %v", val) 980 | } 981 | } 982 | } 983 | 984 | func TestIsZero(t *testing.T) { 985 | var T = struct { 986 | A string 987 | B int 988 | C bool `structs:"-"` 989 | D []string 990 | }{} 991 | 992 | ok := IsZero(T) 993 | if !ok { 994 | t.Error("IsZero should return true because none of the fields are initialized.") 995 | } 996 | 997 | var X = struct { 998 | A string 999 | F *bool 1000 | }{ 1001 | A: "a-value", 1002 | } 1003 | 1004 | ok = IsZero(X) 1005 | if ok { 1006 | t.Error("IsZero should return false because A is initialized") 1007 | } 1008 | 1009 | var Y = struct { 1010 | A string 1011 | B int 1012 | }{ 1013 | A: "a-value", 1014 | B: 123, 1015 | } 1016 | 1017 | ok = IsZero(Y) 1018 | if ok { 1019 | t.Error("IsZero should return false because A and B is initialized") 1020 | } 1021 | } 1022 | 1023 | func TestIsZero_OmitNested(t *testing.T) { 1024 | type A struct { 1025 | Name string 1026 | D string 1027 | } 1028 | a := A{Name: "example"} 1029 | 1030 | type B struct { 1031 | A A `structs:",omitnested"` 1032 | C int 1033 | } 1034 | b := &B{A: a, C: 123} 1035 | 1036 | ok := IsZero(b) 1037 | if ok { 1038 | t.Error("IsZero should return false because A, B and C are initialized") 1039 | } 1040 | 1041 | aZero := A{} 1042 | bZero := &B{A: aZero} 1043 | 1044 | ok = IsZero(bZero) 1045 | if !ok { 1046 | t.Error("IsZero should return true because neither A nor B is initialized") 1047 | } 1048 | 1049 | } 1050 | 1051 | func TestIsZero_Nested(t *testing.T) { 1052 | type A struct { 1053 | Name string 1054 | D string 1055 | } 1056 | a := A{Name: "example"} 1057 | 1058 | type B struct { 1059 | A A 1060 | C int 1061 | } 1062 | b := &B{A: a, C: 123} 1063 | 1064 | ok := IsZero(b) 1065 | if ok { 1066 | t.Error("IsZero should return false because A, B and C are initialized") 1067 | } 1068 | 1069 | aZero := A{} 1070 | bZero := &B{A: aZero} 1071 | 1072 | ok = IsZero(bZero) 1073 | if !ok { 1074 | t.Error("IsZero should return true because neither A nor B is initialized") 1075 | } 1076 | 1077 | } 1078 | 1079 | func TestIsZero_Anonymous(t *testing.T) { 1080 | type A struct { 1081 | Name string 1082 | D string 1083 | } 1084 | a := A{Name: "example"} 1085 | 1086 | type B struct { 1087 | A 1088 | C int 1089 | } 1090 | b := &B{C: 123} 1091 | b.A = a 1092 | 1093 | ok := IsZero(b) 1094 | if ok { 1095 | t.Error("IsZero should return false because A, B and C are initialized") 1096 | } 1097 | 1098 | aZero := A{} 1099 | bZero := &B{} 1100 | bZero.A = aZero 1101 | 1102 | ok = IsZero(bZero) 1103 | if !ok { 1104 | t.Error("IsZero should return true because neither A nor B is initialized") 1105 | } 1106 | } 1107 | 1108 | func TestHasZero(t *testing.T) { 1109 | var T = struct { 1110 | A string 1111 | B int 1112 | C bool `structs:"-"` 1113 | D []string 1114 | }{ 1115 | A: "a-value", 1116 | B: 2, 1117 | } 1118 | 1119 | ok := HasZero(T) 1120 | if !ok { 1121 | t.Error("HasZero should return true because A and B are initialized.") 1122 | } 1123 | 1124 | var X = struct { 1125 | A string 1126 | F *bool 1127 | }{ 1128 | A: "a-value", 1129 | } 1130 | 1131 | ok = HasZero(X) 1132 | if !ok { 1133 | t.Error("HasZero should return true because A is initialized") 1134 | } 1135 | 1136 | var Y = struct { 1137 | A string 1138 | B int 1139 | }{ 1140 | A: "a-value", 1141 | B: 123, 1142 | } 1143 | 1144 | ok = HasZero(Y) 1145 | if ok { 1146 | t.Error("HasZero should return false because A and B is initialized") 1147 | } 1148 | } 1149 | 1150 | func TestHasZero_OmitNested(t *testing.T) { 1151 | type A struct { 1152 | Name string 1153 | D string 1154 | } 1155 | a := A{Name: "example"} 1156 | 1157 | type B struct { 1158 | A A `structs:",omitnested"` 1159 | C int 1160 | } 1161 | b := &B{A: a, C: 123} 1162 | 1163 | // Because the Field A inside B is omitted HasZero should return false 1164 | // because it will stop iterating deeper andnot going to lookup for D 1165 | ok := HasZero(b) 1166 | if ok { 1167 | t.Error("HasZero should return false because A and C are initialized") 1168 | } 1169 | } 1170 | 1171 | func TestHasZero_Nested(t *testing.T) { 1172 | type A struct { 1173 | Name string 1174 | D string 1175 | } 1176 | a := A{Name: "example"} 1177 | 1178 | type B struct { 1179 | A A 1180 | C int 1181 | } 1182 | b := &B{A: a, C: 123} 1183 | 1184 | ok := HasZero(b) 1185 | if !ok { 1186 | t.Error("HasZero should return true because D is not initialized") 1187 | } 1188 | } 1189 | 1190 | func TestHasZero_Anonymous(t *testing.T) { 1191 | type A struct { 1192 | Name string 1193 | D string 1194 | } 1195 | a := A{Name: "example"} 1196 | 1197 | type B struct { 1198 | A 1199 | C int 1200 | } 1201 | b := &B{C: 123} 1202 | b.A = a 1203 | 1204 | ok := HasZero(b) 1205 | if !ok { 1206 | t.Error("HasZero should return false because D is not initialized") 1207 | } 1208 | } 1209 | 1210 | func TestName(t *testing.T) { 1211 | type Foo struct { 1212 | A string 1213 | B bool 1214 | } 1215 | f := &Foo{} 1216 | 1217 | n := Name(f) 1218 | if n != "Foo" { 1219 | t.Errorf("Name should return Foo, got: %s", n) 1220 | } 1221 | 1222 | unnamed := struct{ Name string }{Name: "Cihangir"} 1223 | m := Name(unnamed) 1224 | if m != "" { 1225 | t.Errorf("Name should return empty string for unnamed struct, got: %s", n) 1226 | } 1227 | 1228 | defer func() { 1229 | err := recover() 1230 | if err == nil { 1231 | t.Error("Name should panic if a non struct is passed") 1232 | } 1233 | }() 1234 | 1235 | Name([]string{}) 1236 | } 1237 | 1238 | func TestNestedNilPointer(t *testing.T) { 1239 | type Collar struct { 1240 | Engraving string 1241 | } 1242 | 1243 | type Dog struct { 1244 | Name string 1245 | Collar *Collar 1246 | } 1247 | 1248 | type Person struct { 1249 | Name string 1250 | Dog *Dog 1251 | } 1252 | 1253 | person := &Person{ 1254 | Name: "John", 1255 | } 1256 | 1257 | personWithDog := &Person{ 1258 | Name: "Ron", 1259 | Dog: &Dog{ 1260 | Name: "Rover", 1261 | }, 1262 | } 1263 | 1264 | personWithDogWithCollar := &Person{ 1265 | Name: "Kon", 1266 | Dog: &Dog{ 1267 | Name: "Ruffles", 1268 | Collar: &Collar{ 1269 | Engraving: "If lost, call Kon", 1270 | }, 1271 | }, 1272 | } 1273 | 1274 | defer func() { 1275 | err := recover() 1276 | if err != nil { 1277 | fmt.Printf("err %+v\n", err) 1278 | t.Error("Internal nil pointer should not panic") 1279 | } 1280 | }() 1281 | 1282 | _ = Map(person) // Panics 1283 | _ = Map(personWithDog) // Panics 1284 | _ = Map(personWithDogWithCollar) // Doesn't panic 1285 | } 1286 | 1287 | func TestSetValueOnNestedField(t *testing.T) { 1288 | type Base struct { 1289 | ID int 1290 | } 1291 | 1292 | type User struct { 1293 | Base 1294 | Name string 1295 | } 1296 | 1297 | u := User{} 1298 | s := New(&u) 1299 | f := s.Field("Base").Field("ID") 1300 | err := f.Set(10) 1301 | if err != nil { 1302 | t.Errorf("Error %v", err) 1303 | } 1304 | if f.Value().(int) != 10 { 1305 | t.Errorf("Value should be equal to 10, got %v", f.Value()) 1306 | } 1307 | } 1308 | 1309 | type Person struct { 1310 | Name string 1311 | Age int 1312 | } 1313 | 1314 | func (p *Person) String() string { 1315 | return fmt.Sprintf("%s(%d)", p.Name, p.Age) 1316 | } 1317 | 1318 | func TestTagWithStringOption(t *testing.T) { 1319 | 1320 | type Address struct { 1321 | Country string `json:"country"` 1322 | Person *Person `json:"person,string"` 1323 | } 1324 | 1325 | person := &Person{ 1326 | Name: "John", 1327 | Age: 23, 1328 | } 1329 | 1330 | address := &Address{ 1331 | Country: "EU", 1332 | Person: person, 1333 | } 1334 | 1335 | defer func() { 1336 | err := recover() 1337 | if err != nil { 1338 | fmt.Printf("err %+v\n", err) 1339 | t.Error("Internal nil pointer should not panic") 1340 | } 1341 | }() 1342 | 1343 | s := New(address) 1344 | 1345 | s.TagName = "json" 1346 | m := s.Map() 1347 | 1348 | if m["person"] != person.String() { 1349 | t.Errorf("Value for field person should be %s, got: %s", person.String(), m["person"]) 1350 | } 1351 | 1352 | vs := s.Values() 1353 | if vs[1] != person.String() { 1354 | t.Errorf("Value for 2nd field (person) should be %T, got: %T", person.String(), vs[1]) 1355 | } 1356 | } 1357 | 1358 | type Animal struct { 1359 | Name string 1360 | Age int 1361 | } 1362 | 1363 | type Dog struct { 1364 | Animal *Animal `json:"animal,string"` 1365 | } 1366 | 1367 | func TestNonStringerTagWithStringOption(t *testing.T) { 1368 | a := &Animal{ 1369 | Name: "Fluff", 1370 | Age: 4, 1371 | } 1372 | 1373 | d := &Dog{ 1374 | Animal: a, 1375 | } 1376 | 1377 | defer func() { 1378 | err := recover() 1379 | if err != nil { 1380 | fmt.Printf("err %+v\n", err) 1381 | t.Error("Internal nil pointer should not panic") 1382 | } 1383 | }() 1384 | 1385 | s := New(d) 1386 | 1387 | s.TagName = "json" 1388 | m := s.Map() 1389 | 1390 | if _, exists := m["animal"]; exists { 1391 | t.Errorf("Value for field Animal should not exist") 1392 | } 1393 | } 1394 | 1395 | func TestMap_InterfaceValue(t *testing.T) { 1396 | type TestStruct struct { 1397 | A interface{} 1398 | } 1399 | 1400 | expected := []byte("test value") 1401 | 1402 | a := TestStruct{A: expected} 1403 | s := Map(a) 1404 | if !reflect.DeepEqual(s["A"], expected) { 1405 | t.Errorf("Value does not match expected: %q != %q", s["A"], expected) 1406 | } 1407 | } 1408 | 1409 | func TestPointer2Pointer(t *testing.T) { 1410 | defer func() { 1411 | err := recover() 1412 | if err != nil { 1413 | fmt.Printf("err %+v\n", err) 1414 | t.Error("Internal nil pointer should not panic") 1415 | } 1416 | }() 1417 | a := &Animal{ 1418 | Name: "Fluff", 1419 | Age: 4, 1420 | } 1421 | _ = Map(&a) 1422 | 1423 | b := &a 1424 | _ = Map(&b) 1425 | 1426 | c := &b 1427 | _ = Map(&c) 1428 | } 1429 | 1430 | func TestMap_InterfaceTypeWithMapValue(t *testing.T) { 1431 | type A struct { 1432 | Name string `structs:"name"` 1433 | IP string `structs:"ip"` 1434 | Query string `structs:"query"` 1435 | Payload interface{} `structs:"payload"` 1436 | } 1437 | 1438 | a := A{ 1439 | Name: "test", 1440 | IP: "127.0.0.1", 1441 | Query: "", 1442 | Payload: map[string]string{"test_param": "test_param"}, 1443 | } 1444 | 1445 | defer func() { 1446 | err := recover() 1447 | if err != nil { 1448 | t.Error("Converting Map with an interface{} type with map value should not panic") 1449 | } 1450 | }() 1451 | 1452 | _ = Map(a) 1453 | } 1454 | -------------------------------------------------------------------------------- /tags.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import "strings" 4 | 5 | // tagOptions contains a slice of tag options 6 | type tagOptions []string 7 | 8 | // Has returns true if the given option is available in tagOptions 9 | func (t tagOptions) Has(opt string) bool { 10 | for _, tagOpt := range t { 11 | if tagOpt == opt { 12 | return true 13 | } 14 | } 15 | 16 | return false 17 | } 18 | 19 | // parseTag splits a struct field's tag into its name and a list of options 20 | // which comes after a name. A tag is in the form of: "name,option1,option2". 21 | // The name can be neglectected. 22 | func parseTag(tag string) (string, tagOptions) { 23 | // tag is one of followings: 24 | // "" 25 | // "name" 26 | // "name,opt" 27 | // "name,opt,opt2" 28 | // ",opt" 29 | 30 | res := strings.Split(tag, ",") 31 | return res[0], res[1:] 32 | } 33 | -------------------------------------------------------------------------------- /tags_test.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import "testing" 4 | 5 | func TestParseTag_Name(t *testing.T) { 6 | tags := []struct { 7 | tag string 8 | has bool 9 | }{ 10 | {"", false}, 11 | {"name", true}, 12 | {"name,opt", true}, 13 | {"name , opt, opt2", false}, // has a single whitespace 14 | {", opt, opt2", false}, 15 | } 16 | 17 | for _, tag := range tags { 18 | name, _ := parseTag(tag.tag) 19 | 20 | if (name != "name") && tag.has { 21 | t.Errorf("Parse tag should return name: %#v", tag) 22 | } 23 | } 24 | } 25 | 26 | func TestParseTag_Opts(t *testing.T) { 27 | tags := []struct { 28 | opts string 29 | has bool 30 | }{ 31 | {"name", false}, 32 | {"name,opt", true}, 33 | {"name , opt, opt2", false}, // has a single whitespace 34 | {",opt, opt2", true}, 35 | {", opt3, opt4", false}, 36 | } 37 | 38 | // search for "opt" 39 | for _, tag := range tags { 40 | _, opts := parseTag(tag.opts) 41 | 42 | if opts.Has("opt") != tag.has { 43 | t.Errorf("Tag opts should have opt: %#v", tag) 44 | } 45 | } 46 | } 47 | --------------------------------------------------------------------------------