Skip to content

Commit

Permalink
Merge pull request #9 from intelligentpos/feature-support-embeded-str…
Browse files Browse the repository at this point in the history
…ucts

Adding support for embedded structs
  • Loading branch information
geototti21 authored Feb 13, 2018
2 parents e1662b0 + b797d70 commit 1de0807
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 49 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,26 @@ A sample example that we use the structextract with [Squirrel](https://github.co
//Make the sql request..
```

#### Now with support for embedded structs
```go
type SampleInner struct {
Inner string `json:"inner"`
}

type SampleOuter struct {
SampleInner
Field string `json:"field"`
}

ss := SampleOuter{SampleInner{"inner"}, "outer"}

ext := structextract.New(&ss).UseEmbeddedStructs(true)

jsonMap, err := ext.FieldValueFromTagMap("json")
// jsonMap here would be equal to the following map
m := map[string]interface{}{
"field": "outer",
"inner": "inner",
}
```

126 changes: 77 additions & 49 deletions extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import (

// Extractor holds the struct that we want to extract data from
type Extractor struct {
StructAddr interface{} // StructAddr: struct address
ignoredFields []string // ignoredFields: an array with all the fields to be ignored
StructAddr interface{} // StructAddr: struct address
ignoredFields []string // ignoredFields: an array with all the fields to be ignored
useEmbeddedStructs bool
}

// New returns a new Extractor struct
// the parameter have to be a pointer to a struct
func New(s interface{}) *Extractor {
return &Extractor{
StructAddr: s,
ignoredFields: nil,
StructAddr: s,
ignoredFields: nil,
useEmbeddedStructs: false,
}
}

Expand All @@ -29,11 +31,9 @@ func (e *Extractor) Names() (out []string, err error) {
}

s := reflect.ValueOf(e.StructAddr).Elem()
for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}
out = append(out, s.Type().Field(i).Name)
fields := e.fields(s)
for _, field := range fields {
out = append(out, field.name)
}

return
Expand All @@ -47,12 +47,10 @@ func (e *Extractor) NamesFromTag(tag string) (out []string, err error) {
}

s := reflect.ValueOf(e.StructAddr).Elem()
fields := e.fields(s)

for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}
if val, ok := s.Type().Field(i).Tag.Lookup(tag); ok {
for _, field := range fields {
if val, ok := field.tags.Lookup(tag); ok {
out = append(out, val)
}
}
Expand All @@ -68,12 +66,10 @@ func (e *Extractor) NamesFromTagWithPrefix(tag string, prefix string) (out []str
}

s := reflect.ValueOf(e.StructAddr).Elem()
fields := e.fields(s)

for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}
val, ok := s.Type().Field(i).Tag.Lookup(tag)
for _, field := range fields {
val, ok := field.tags.Lookup(tag)
if !ok {
continue
}
Expand All @@ -91,11 +87,10 @@ func (e *Extractor) Values() (out []interface{}, err error) {
}

s := reflect.ValueOf(e.StructAddr).Elem()
for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}
out = append(out, s.Field(i).Interface())
fields := e.fields(s)

for _, field := range fields {
out = append(out, field.value.Interface())

}

Expand All @@ -110,12 +105,11 @@ func (e *Extractor) ValuesFromTag(tag string) (out []interface{}, err error) {
}

s := reflect.ValueOf(e.StructAddr).Elem()
for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}
if _, ok := s.Type().Field(i).Tag.Lookup(tag); ok {
out = append(out, s.Field(i).Interface())
fields := e.fields(s)

for _, field := range fields {
if _, ok := field.tags.Lookup(tag); ok {
out = append(out, field.value.Interface())
}

}
Expand All @@ -134,11 +128,10 @@ func (e *Extractor) FieldValueMap() (out map[string]interface{}, err error) {

out = make(map[string]interface{})
s := reflect.ValueOf(e.StructAddr).Elem()
for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}
out[s.Type().Field(i).Name] = s.Field(i).Interface()
fields := e.fields(s)

for _, field := range fields {
out[field.name] = field.value.Interface()
}

return
Expand All @@ -155,13 +148,11 @@ func (e *Extractor) FieldValueFromTagMap(tag string) (out map[string]interface{}

out = make(map[string]interface{})
s := reflect.ValueOf(e.StructAddr).Elem()
for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}
fields := e.fields(s)

if val, ok := s.Type().Field(i).Tag.Lookup(tag); ok {
out[val] = s.Field(i).Interface()
for _, field := range fields {
if val, ok := field.tags.Lookup(tag); ok {
out[val] = field.value.Interface()
}

}
Expand All @@ -180,13 +171,11 @@ func (e *Extractor) TagMapping(from, to string) (out map[string]string, err erro

out = make(map[string]string)
s := reflect.ValueOf(e.StructAddr).Elem()
for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}
fields := e.fields(s)

fromTag, fromOk := s.Type().Field(i).Tag.Lookup(from)
toTag, toOk := s.Type().Field(i).Tag.Lookup(to)
for _, field := range fields {
fromTag, fromOk := field.tags.Lookup(from)
toTag, toOk := field.tags.Lookup(to)
if toOk && fromOk {
out[fromTag] = toTag
}
Expand All @@ -212,12 +201,19 @@ func (e *Extractor) IgnoreField(fd ...string) *Extractor {
return e
}

// UseEmbeddedStructs toggles the usage of embedded structs
func (e *Extractor) UseEmbeddedStructs(use bool) *Extractor {
e.useEmbeddedStructs = use
return e
}

func (e *Extractor) isFieldNameValid(fn string) bool {

s := reflect.ValueOf(e.StructAddr).Elem()
fields := e.fields(s)

for i := 0; i < s.NumField(); i++ {
if s.Type().Field(i).Name == fn {
for _, field := range fields {
if field.name == fn {
return true
}
}
Expand Down Expand Up @@ -247,3 +243,35 @@ func (e *Extractor) isValidStruct() error {

return nil
}

type field struct {
value reflect.Value
name string
tags reflect.StructTag
}

// This function returns a slice of fields of a struct
// as reflect.Value, even fields of embedded structs
func (e *Extractor) fields(s reflect.Value) []field {
fields := make([]field, 0, s.NumField())

for i := 0; i < s.NumField(); i++ {
if isIgnored(s.Type().Field(i).Name, e.ignoredFields) {
continue
}

if s.Type().Field(i).Anonymous {
if e.useEmbeddedStructs {
fields = append(fields, e.fields(s.Field(i))...)
}
continue
}

tag := s.Type().Field(i).Tag
name := s.Type().Field(i).Name
value := s.Field(i)
fields = append(fields, field{value, name, tag})
}

return fields
}
35 changes: 35 additions & 0 deletions extract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,3 +433,38 @@ func TestTagMapping_invalidStruct(t *testing.T) {
t.Fatal("Passed value is not a valid struct")
}
}

func TestEmbeddedStructs_togglingBehaviour(t *testing.T) {
type Embed struct {
AnotherField string
}
type Outer struct {
Embed
Field string
}

ts := Outer{Embed{"another"}, "some"}

ext := New(&ts)
v, err := ext.Values()
if err != nil {
t.Fatal("failed to get values when not using embedded structs")
}

if len(v) != 1 {
t.Fatalf("expected a single value, got %d", len(v))
}

if !reflect.DeepEqual(v[0], "some") {
t.Fatalf("expected the single value to be 'some', got %v", v[0])
}

v, err = ext.UseEmbeddedStructs(true).Values()
if err != nil {
t.Fatalf("failed to get values when using embedded structs: %s", err)
}

if len(v) != 2 {
t.Fatalf("expected to get 2 values when using embedded struct")
}
}

0 comments on commit 1de0807

Please sign in to comment.