Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for map of struct #29

Merged
merged 3 commits into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ type Sample struct {

Slice []string `default:"[]"`
SliceByJSON []int `default:"[1, 2, 3]"` // Supports JSON
Map map[string]int `default:"{}"`
MapByJSON map[string]int `default:"{\"foo\": 123}"`

Map map[string]int `default:"{}"`
MapByJSON map[string]int `default:"{\"foo\": 123}"`
MapOfStruct map[string]OtherStruct
MapOfPtrStruct map[string]*OtherStruct
MapOfStructWithTag map[string]OtherStruct `default:"{\"Key1\": {\"Foo\":123}}"`

Struct OtherStruct `default:"{}"`
StructPtr *OtherStruct `default:"{\"Foo\": 123}"`

Expand Down
27 changes: 27 additions & 0 deletions defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,31 @@ func setField(field reflect.Value, defaultVal string) error {
return err
}
}
case reflect.Map:
for _, e := range field.MapKeys() {
var v = field.MapIndex(e)
var baseVal interface{}
switch v.Kind() {
case reflect.Ptr:
baseVal = v.Elem().Interface()
default:
baseVal = v.Interface()
}
ref := reflect.New(reflect.TypeOf(baseVal))
ref.Elem().Set(reflect.ValueOf(baseVal))
err := Set(ref.Interface())
if err == nil || err == errInvalidType {
var newVal reflect.Value
if v.Kind() == reflect.Ptr {
newVal = reflect.ValueOf(ref.Interface())
} else {
newVal = reflect.ValueOf(ref.Elem().Interface())
}
field.SetMapIndex(e, newVal)
} else {
return err
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delving in on my side, this feels more elegant and can support even more complex types like map[string]map[int]Struct, map[string]*map[int]Struct,map[string]AliasOfSliceOfStruct, etc.

		for _, e := range field.MapKeys() {
			var v = field.MapIndex(e)

			switch v.Kind() {
			case reflect.Ptr:
				switch v.Elem().Kind() {
				case reflect.Struct, reflect.Slice, reflect.Map:
					if err := setField(v.Elem(), ""); err != nil {
						return err
					}
				}
			case reflect.Struct, reflect.Slice, reflect.Map:
				ref := reflect.New(v.Type())
				ref.Elem().Set(v)
				if err := setField(ref.Elem(), ""); err != nil {
					return err
				}
				field.SetMapIndex(e, ref.Elem().Convert(v.Type()))
			}
		}

}
}

return nil
Expand All @@ -187,6 +212,8 @@ func shouldInitializeField(field reflect.Value, tag string) bool {
}
case reflect.Slice:
return field.Len() > 0 || tag != ""
case reflect.Map:
return field.Len() > 0 || tag != ""
}

return tag != ""
Expand Down
77 changes: 76 additions & 1 deletion defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ type Sample struct {
StructWithJSON Struct `default:"{\"Foo\": 123}"`
StructPtrWithJSON *Struct `default:"{\"Foo\": 123}"`
MapWithJSON map[string]int `default:"{\"foo\": 123}"`
SliceWithJSON []string `default:"[\"foo\"]"`

MapOfPtrStruct map[string]*Struct
MapOfStruct map[string]Struct
MapOfStructWithTag map[string]Struct `default:"{\"Struct3\": {\"Foo\":123}}"`

SliceWithJSON []string `default:"[\"foo\"]"`

Empty string `default:""`

Expand Down Expand Up @@ -203,6 +208,13 @@ func TestInit(t *testing.T) {
NonInitialStruct: Struct{Foo: 123},
NonInitialStructPtr: &Struct{Foo: 123},
DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}},
MapOfStruct: map[string]Struct{
"Struct1": {Foo: 1},
},
MapOfPtrStruct: map[string]*Struct{
"Struct1": {Foo: 1},
"Struct2": {Bar: 5},
},
}

if err := Set(sample); err != nil {
Expand Down Expand Up @@ -560,6 +572,69 @@ func TestInit(t *testing.T) {
}
})

t.Run("map of struct", func(t *testing.T) {
if sample.MapOfStruct == nil {
t.Errorf("it should not unset an initiated map")
}
if len(sample.MapOfStruct) != 1 {
t.Errorf("it should not override an initiated map")
}
if sample.MapOfStruct["Struct1"].Foo != 1 {
t.Errorf("it should not override Foo field in Struct1 item")
}
if sample.MapOfStruct["Struct1"].Bar != 456 {
t.Errorf("it should set default for Bar field in Struct1 item")
}
if sample.MapOfStruct["Struct1"].WithDefault != "foo" {
t.Errorf("it should set default for WithDefault field in Struct1 item")
}
})

t.Run("map of ptr struct", func(t *testing.T) {
if sample.MapOfPtrStruct == nil {
t.Errorf("it should not unset an initiated map")
}
if len(sample.MapOfPtrStruct) != 2 {
t.Errorf("it should not override an initiated map")
}
if sample.MapOfPtrStruct["Struct1"].Foo != 1 {
t.Errorf("it should not override Foo field in Struct1 item")
}
if sample.MapOfPtrStruct["Struct1"].Bar != 456 {
t.Errorf("it should set default for Bar field in Struct1 item")
}
if sample.MapOfPtrStruct["Struct1"].WithDefault != "foo" {
t.Errorf("it should set default for WithDefault field in Struct1 item")
}
if sample.MapOfPtrStruct["Struct2"].Foo != 0 {
t.Errorf("it should not override Foo field in Struct2 item")
}
if sample.MapOfPtrStruct["Struct2"].Bar != 456 {
t.Errorf("it should using setter to set default for Bar field in a Struct2 item")
}
if sample.MapOfPtrStruct["Struct2"].WithDefault != "foo" {
t.Errorf("it should set default for WithDefault field in Struct2 item")
}
})

t.Run("map of struct with tag", func(t *testing.T) {
if sample.MapOfStructWithTag == nil {
t.Errorf("it should set default")
}
if len(sample.MapOfStructWithTag) != 1 {
t.Errorf("it should set default with correct value")
}
if sample.MapOfStructWithTag["Struct3"].Foo != 123 {
t.Errorf("it should set default with correct value (Foo)")
}
if sample.MapOfStructWithTag["Struct1"].Bar != 0 {
t.Errorf("it should set default with correct value (Bar)")
}
if sample.MapOfStructWithTag["Struct1"].WithDefault != "" {
t.Errorf("it should set default with correct value (WithDefault)")
}
})

t.Run("opt-out", func(t *testing.T) {
if sample.NoDefault != nil {
t.Errorf("it should not be set")
Expand Down