diff --git a/README.md b/README.md index 975f6d0..de1ca3d 100644 --- a/README.md +++ b/README.md @@ -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}"` diff --git a/defaults.go b/defaults.go index d4baf0f..61856e7 100644 --- a/defaults.go +++ b/defaults.go @@ -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 + } + } } return nil @@ -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 != "" diff --git a/defaults_test.go b/defaults_test.go index 90f185d..5bfba7b 100644 --- a/defaults_test.go +++ b/defaults_test.go @@ -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:""` @@ -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 { @@ -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")