diff --git a/structs.go b/structs.go index f3dcf9a..7ac8836 100644 --- a/structs.go +++ b/structs.go @@ -1,7 +1,11 @@ // Package structs contains various utilities functions to work with structs. package structs -import "reflect" +import ( + "fmt" + + "reflect" +) var ( // DefaultTagName is the default tag name for struct fields which provides @@ -42,6 +46,12 @@ func New(s interface{}) *Struct { // // Field is ignored by this package. // Field bool `structs:"-"` // +// A tag value with the content of "string" uses the stringer to get the value. Example: +// +// // The value will be output of Animal's String() func. +// // Map will panic if Animal does not implement String(). +// Field *Animal `structs:"field,string"` +// // A tag value with the option of "omitnested" stops iterating further if the type // is a struct. Example: // @@ -104,6 +114,14 @@ func (s *Struct) Map() map[string]interface{} { finalVal = val.Interface() } + if tagOpts.Has("string") { + s, ok := val.Interface().(fmt.Stringer) + if ok { + out[name] = s.String() + } + continue + } + out[name] = finalVal } @@ -153,6 +171,14 @@ func (s *Struct) Values() []interface{} { } } + if tagOpts.Has("string") { + s, ok := val.Interface().(fmt.Stringer) + if ok { + t = append(t, s.String()) + } + continue + } + if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { // look out for embedded structs, and convert them to a // []interface{} to be added to the final values slice diff --git a/structs_test.go b/structs_test.go index 43af9c8..7402bc0 100644 --- a/structs_test.go +++ b/structs_test.go @@ -910,3 +910,89 @@ func TestNestedNilPointer(t *testing.T) { _ = Map(personWithDog) // Panics _ = Map(personWithDogWithCollar) // Doesn't panic } + +type Person struct { + Name string + Age int +} + +func (p *Person) String() string { + return fmt.Sprintf("%s(%d)", p.Name, p.Age) +} + +func TestTagWithStringOption(t *testing.T) { + + type Address struct { + Country string `json:"country"` + Person *Person `json:"person,string"` + } + + person := &Person{ + Name: "John", + Age: 23, + } + + address := &Address{ + Country: "EU", + Person: person, + } + + defer func() { + err := recover() + if err != nil { + fmt.Printf("err %+v\n", err) + t.Error("Internal nil pointer should not panic") + } + }() + + s := New(address) + + s.TagName = "json" + m := s.Map() + + if m["person"] != person.String() { + t.Errorf("Value for field person should be %s, got: %s", person.String(), m["person"]) + } + + vs := s.Values() + if vs[1] != person.String() { + t.Errorf("Value for 2nd field (person) should be %t, got: %t", person.String(), vs[1]) + } +} + +type Animal struct { + Name string + Age int +} + +type Dog struct { + Animal *Animal `json:"animal,string"` +} + +func TestNonStringerTagWithStringOption(t *testing.T) { + a := &Animal{ + Name: "Fluff", + Age: 4, + } + + d := &Dog{ + Animal: a, + } + + defer func() { + err := recover() + if err != nil { + fmt.Printf("err %+v\n", err) + t.Error("Internal nil pointer should not panic") + } + }() + + s := New(d) + + s.TagName = "json" + m := s.Map() + + if _, exists := m["animal"]; exists { + t.Errorf("Value for field Animal should not exist") + } +}