diff --git a/README.md b/README.md index e8096b6..8561aeb 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Initialize structs with default values - Recursively initializes fields in a struct - Dynamically sets default values by [`defaults.Setter`](./setter.go) interface - Preserves non-initial values from being reset with a default value +- User-Definable tag name Usage @@ -43,30 +44,30 @@ import ( type Gender string type Sample struct { - Name string `default:"John Smith"` - Age int `default:"27"` - Gender Gender `default:"m"` + Name string `default:"John Smith" default_alt:"Jane Doe"` + Age int `default:"27" default_alt:"28"` + Gender Gender `default:"m" default_alt:"f"` Working bool `default:"true"` - SliceInt []int `default:"[1, 2, 3]"` - SlicePtr []*int `default:"[1, 2, 3]"` - SliceString []string `default:"[\"a\", \"b\"]"` + SliceInt []int `default:"[1, 2, 3]" default_alt:"[4, 5, 6]"` + SlicePtr []*int `default:"[1, 2, 3]" default_alt:"[4, 5, 6]"` + SliceString []string `default:"[\"a\", \"b\"]" default_alt:"[\"c\", \"d\"]"` - MapNull map[string]int `default:"{}"` - Map map[string]int `default:"{\"key1\": 123}"` - MapOfStruct map[string]OtherStruct `default:"{\"Key2\": {\"Foo\":123}}"` - MapOfPtrStruct map[string]*OtherStruct `default:"{\"Key3\": {\"Foo\":123}}"` - MapOfStructWithTag map[string]OtherStruct `default:"{\"Key4\": {\"Foo\":123}}"` + MapNull map[string]int `default:"{}"` // Using default_alt would leave as a nil map + Map map[string]int `default:"{\"key1\": 123}" default_alt:"{\"key1\": 456}"` + MapOfStruct map[string]OtherStruct `default:"{\"Key2\": {\"Foo\":123}}" default_alt:"{\"Key2\": {\"Foo\":456}}"` + MapOfPtrStruct map[string]*OtherStruct `default:"{\"Key3\": {\"Foo\":123}}" default_alt:"{\"Key3\": {\"Foo\":456}}"` + MapOfStructWithTag map[string]OtherStruct `default:"{\"Key4\": {\"Foo\":123}}" default_alt:"{\"Key4\": {\"Foo\":456}}"` - Struct OtherStruct `default:"{\"Foo\": 123}"` - StructPtr *OtherStruct `default:"{\"Foo\": 123}"` + Struct OtherStruct `default:"{\"Foo\": 123}" default:"{\"Foo\": 456}"` + StructPtr *OtherStruct `default:"{\"Foo\": 123}" default:"{\"Foo\": 456}"` NoTag OtherStruct // Recurses into a nested struct by default NoOption OtherStruct `default:"-"` // no option } type OtherStruct struct { - Hello string `default:"world"` // Tags in a nested struct also work + Hello string `default:"world" default_alt:"person"` // Tags in a nested struct also work Foo int `default:"-"` Random int `default:"-"` } @@ -91,70 +92,149 @@ func main() { fmt.Println(string(out)) // Output: - // { - // "Name": "John Smith", - // "Age": 27, - // "Gender": "m", - // "Working": true, - // "SliceInt": [ - // 1, - // 2, - // 3 - // ], - // "SlicePtr": [ - // 1, - // 2, - // 3 - // ], - // "SliceString": [ - // "a", - // "b" - // ], - // "MapNull": {}, - // "Map": { - // "key1": 123 - // }, - // "MapOfStruct": { - // "Key2": { - // "Hello": "world", - // "Foo": 123, - // "Random": 5577006791947779410 - // } - // }, - // "MapOfPtrStruct": { - // "Key3": { - // "Hello": "world", - // "Foo": 123, - // "Random": 8674665223082153551 - // } - // }, - // "MapOfStructWithTag": { - // "Key4": { - // "Hello": "world", - // "Foo": 123, - // "Random": 6129484611666145821 - // } - // }, - // "Struct": { - // "Hello": "world", - // "Foo": 123, - // "Random": 4037200794235010051 - // }, - // "StructPtr": { - // "Hello": "world", - // "Foo": 123, - // "Random": 3916589616287113937 - // }, - // "NoTag": { - // "Hello": "world", - // "Foo": 0, - // "Random": 6334824724549167320 - // }, - // "NoOption": { - // "Hello": "", - // "Foo": 0, - // "Random": 0 - // } - // } + /* + { + "Name": "John Smith", + "Age": 27, + "Gender": "m", + "Working": true, + "SliceInt": [ + 1, + 2, + 3 + ], + "SlicePtr": [ + 1, + 2, + 3 + ], + "SliceString": [ + "a", + "b" + ], + "MapNull": {}, + "Map": { + "key1": 123 + }, + "MapOfStruct": { + "Key2": { + "Hello": "world", + "Foo": 123, + "Random": 7924310560672650386 + } + }, + "MapOfPtrStruct": { + "Key3": { + "Hello": "world", + "Foo": 123, + "Random": 5864827752208870454 + } + }, + "MapOfStructWithTag": { + "Key4": { + "Hello": "world", + "Foo": 123, + "Random": 9020930397672710767 + } + }, + "Struct": { + "Hello": "world", + "Foo": 123, + "Random": 2100809313953071154 + }, + "StructPtr": { + "Hello": "world", + "Foo": 123, + "Random": 4589638854976076769 + }, + "NoTag": { + "Hello": "world", + "Foo": 0, + "Random": 93481895446446669 + }, + "NoOption": { + "Hello": "", + "Foo": 0, + "Random": 0 + } + } + */ + + // Or with a specific tag name: + otherObj := &Sample{} + if err = defaults.SetWithTag(otherObj, "default_alt"); err != nil { + panic(err) + } + + out, err = json.MarshalIndent(otherObj, "", " ") + if err != nil { + panic(err) + } + fmt.Println(string(out)) + + // Output: + /* + { + "Name": "Jane Doe", + "Age": 28, + "Gender": "f", + "Working": false, + "SliceInt": [ + 4, + 5, + 6 + ], + "SlicePtr": [ + 4, + 5, + 6 + ], + "SliceString": [ + "c", + "d" + ], + "MapNull": null, + "Map": { + "key1": 456 + }, + "MapOfStruct": { + "Key2": { + "Hello": "person", + "Foo": 456, + "Random": 3527321973631593020 + } + }, + "MapOfPtrStruct": { + "Key3": { + "Hello": "person", + "Foo": 456, + "Random": 6391170103701304261 + } + }, + "MapOfStructWithTag": { + "Key4": { + "Hello": "person", + "Foo": 456, + "Random": 5779747168002044569 + } + }, + "Struct": { + "Hello": "person", + "Foo": 0, + "Random": 4391597250420769051 + }, + "StructPtr": null, + "NoTag": { + "Hello": "person", + "Foo": 0, + "Random": 5000048969203402981 + }, + "NoOption": { + "Hello": "person", + "Foo": 0, + "Random": 4800395242796521920 + } + } + */ } ``` diff --git a/defaults.go b/defaults.go index f453928..00b8e38 100644 --- a/defaults.go +++ b/defaults.go @@ -6,25 +6,38 @@ import ( "errors" "reflect" "strconv" + "strings" "time" ) var ( + errInvalidTag = errors.New("invalid tag name") errInvalidType = errors.New("not a struct pointer") ) -const ( - fieldName = "default" -) - // Set initializes members in a struct referenced by a pointer. // Maps and slices are initialized by `make` and other primitive types are set with default values. // `ptr` should be a struct pointer -func Set(ptr interface{}) error { +func Set(ptr interface{}) (err error) { + + err = SetWithTag(ptr, "default") + + return +} + +// SetWithTag initializes members in a struct referenced by a pointer using an explicit tag name. +// Maps and slices are initialized by `make` and other primitive types are set with default values. +// `ptr` should be a struct pointer +func SetWithTag(ptr interface{}, runtimeTag string) error { + if reflect.TypeOf(ptr).Kind() != reflect.Ptr { return errInvalidType } + if strings.TrimSpace(runtimeTag) == "" { + return errInvalidTag + } + v := reflect.ValueOf(ptr).Elem() t := v.Type() @@ -33,8 +46,8 @@ func Set(ptr interface{}) error { } for i := 0; i < t.NumField(); i++ { - if defaultVal := t.Field(i).Tag.Get(fieldName); defaultVal != "-" { - if err := setField(v.Field(i), defaultVal); err != nil { + if defaultVal := t.Field(i).Tag.Get(runtimeTag); defaultVal != "-" { + if err := setField(v.Field(i), defaultVal, runtimeTag); err != nil { return err } } @@ -51,7 +64,15 @@ func MustSet(ptr interface{}) { } } -func setField(field reflect.Value, defaultVal string) error { +// MustSetWithTag function is a wrapper of SetWithTag function +// It will call Set and panic if err not equals nil. +func MustSetWithTag(ptr interface{}, runtimeTag string) { + if err := SetWithTag(ptr, runtimeTag); err != nil { + panic(err) + } +} + +func setField(field reflect.Value, defaultVal string, tagName string) error { if !field.CanSet() { return nil } @@ -160,16 +181,16 @@ func setField(field reflect.Value, defaultVal string) error { switch field.Kind() { case reflect.Ptr: if isInitial || field.Elem().Kind() == reflect.Struct { - setField(field.Elem(), defaultVal) + setField(field.Elem(), defaultVal, tagName) callSetter(field.Interface()) } case reflect.Struct: - if err := Set(field.Addr().Interface()); err != nil { + if err := SetWithTag(field.Addr().Interface(), tagName); err != nil { return err } case reflect.Slice: for j := 0; j < field.Len(); j++ { - if err := setField(field.Index(j), ""); err != nil { + if err := setField(field.Index(j), "", tagName); err != nil { return err } } @@ -181,14 +202,14 @@ func setField(field reflect.Value, defaultVal string) error { case reflect.Ptr: switch v.Elem().Kind() { case reflect.Struct, reflect.Slice, reflect.Map: - if err := setField(v.Elem(), ""); err != nil { + if err := setField(v.Elem(), "", tagName); 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 { + if err := setField(ref.Elem(), "", tagName); err != nil { return err } field.SetMapIndex(e, ref.Elem().Convert(v.Type())) diff --git a/example/main.go b/example/main.go index 8388350..f46b545 100644 --- a/example/main.go +++ b/example/main.go @@ -11,30 +11,30 @@ import ( type Gender string type Sample struct { - Name string `default:"John Smith"` - Age int `default:"27"` - Gender Gender `default:"m"` + Name string `default:"John Smith" default_alt:"Jane Doe"` + Age int `default:"27" default_alt:"28"` + Gender Gender `default:"m" default_alt:"f"` Working bool `default:"true"` - SliceInt []int `default:"[1, 2, 3]"` - SlicePtr []*int `default:"[1, 2, 3]"` - SliceString []string `default:"[\"a\", \"b\"]"` + SliceInt []int `default:"[1, 2, 3]" default_alt:"[4, 5, 6]"` + SlicePtr []*int `default:"[1, 2, 3]" default_alt:"[4, 5, 6]"` + SliceString []string `default:"[\"a\", \"b\"]" default_alt:"[\"c\", \"d\"]"` - MapNull map[string]int `default:"{}"` - Map map[string]int `default:"{\"key1\": 123}"` - MapOfStruct map[string]OtherStruct `default:"{\"Key2\": {\"Foo\":123}}"` - MapOfPtrStruct map[string]*OtherStruct `default:"{\"Key3\": {\"Foo\":123}}"` - MapOfStructWithTag map[string]OtherStruct `default:"{\"Key4\": {\"Foo\":123}}"` + MapNull map[string]int `default:"{}"` // Using default_alt would leave as a nil map + Map map[string]int `default:"{\"key1\": 123}" default_alt:"{\"key1\": 456}"` + MapOfStruct map[string]OtherStruct `default:"{\"Key2\": {\"Foo\":123}}" default_alt:"{\"Key2\": {\"Foo\":456}}"` + MapOfPtrStruct map[string]*OtherStruct `default:"{\"Key3\": {\"Foo\":123}}" default_alt:"{\"Key3\": {\"Foo\":456}}"` + MapOfStructWithTag map[string]OtherStruct `default:"{\"Key4\": {\"Foo\":123}}" default_alt:"{\"Key4\": {\"Foo\":456}}"` - Struct OtherStruct `default:"{\"Foo\": 123}"` - StructPtr *OtherStruct `default:"{\"Foo\": 123}"` + Struct OtherStruct `default:"{\"Foo\": 123}" default:"{\"Foo\": 456}"` + StructPtr *OtherStruct `default:"{\"Foo\": 123}" default:"{\"Foo\": 456}"` NoTag OtherStruct // Recurses into a nested struct by default NoOption OtherStruct `default:"-"` // no option } type OtherStruct struct { - Hello string `default:"world"` // Tags in a nested struct also work + Hello string `default:"world" default_alt:"person"` // Tags in a nested struct also work Foo int `default:"-"` Random int `default:"-"` } @@ -59,69 +59,148 @@ func main() { fmt.Println(string(out)) // Output: - // { - // "Name": "John Smith", - // "Age": 27, - // "Gender": "m", - // "Working": true, - // "SliceInt": [ - // 1, - // 2, - // 3 - // ], - // "SlicePtr": [ - // 1, - // 2, - // 3 - // ], - // "SliceString": [ - // "a", - // "b" - // ], - // "MapNull": {}, - // "Map": { - // "key1": 123 - // }, - // "MapOfStruct": { - // "Key2": { - // "Hello": "world", - // "Foo": 123, - // "Random": 5577006791947779410 - // } - // }, - // "MapOfPtrStruct": { - // "Key3": { - // "Hello": "world", - // "Foo": 123, - // "Random": 8674665223082153551 - // } - // }, - // "MapOfStructWithTag": { - // "Key4": { - // "Hello": "world", - // "Foo": 123, - // "Random": 6129484611666145821 - // } - // }, - // "Struct": { - // "Hello": "world", - // "Foo": 123, - // "Random": 4037200794235010051 - // }, - // "StructPtr": { - // "Hello": "world", - // "Foo": 123, - // "Random": 3916589616287113937 - // }, - // "NoTag": { - // "Hello": "world", - // "Foo": 0, - // "Random": 6334824724549167320 - // }, - // "NoOption": { - // "Hello": "", - // "Foo": 0, - // "Random": 0 - // } - // } + /* + { + "Name": "John Smith", + "Age": 27, + "Gender": "m", + "Working": true, + "SliceInt": [ + 1, + 2, + 3 + ], + "SlicePtr": [ + 1, + 2, + 3 + ], + "SliceString": [ + "a", + "b" + ], + "MapNull": {}, + "Map": { + "key1": 123 + }, + "MapOfStruct": { + "Key2": { + "Hello": "world", + "Foo": 123, + "Random": 7924310560672650386 + } + }, + "MapOfPtrStruct": { + "Key3": { + "Hello": "world", + "Foo": 123, + "Random": 5864827752208870454 + } + }, + "MapOfStructWithTag": { + "Key4": { + "Hello": "world", + "Foo": 123, + "Random": 9020930397672710767 + } + }, + "Struct": { + "Hello": "world", + "Foo": 123, + "Random": 2100809313953071154 + }, + "StructPtr": { + "Hello": "world", + "Foo": 123, + "Random": 4589638854976076769 + }, + "NoTag": { + "Hello": "world", + "Foo": 0, + "Random": 93481895446446669 + }, + "NoOption": { + "Hello": "", + "Foo": 0, + "Random": 0 + } + } + */ + + // Or with a specific tag name: + otherObj := &Sample{} + if err = defaults.SetWithTag(otherObj, "default_alt"); err != nil { + panic(err) + } + + out, err = json.MarshalIndent(otherObj, "", " ") + if err != nil { + panic(err) + } + fmt.Println(string(out)) + + // Output: + /* + { + "Name": "Jane Doe", + "Age": 28, + "Gender": "f", + "Working": false, + "SliceInt": [ + 4, + 5, + 6 + ], + "SlicePtr": [ + 4, + 5, + 6 + ], + "SliceString": [ + "c", + "d" + ], + "MapNull": null, + "Map": { + "key1": 456 + }, + "MapOfStruct": { + "Key2": { + "Hello": "person", + "Foo": 456, + "Random": 3527321973631593020 + } + }, + "MapOfPtrStruct": { + "Key3": { + "Hello": "person", + "Foo": 456, + "Random": 6391170103701304261 + } + }, + "MapOfStructWithTag": { + "Key4": { + "Hello": "person", + "Foo": 456, + "Random": 5779747168002044569 + } + }, + "Struct": { + "Hello": "person", + "Foo": 0, + "Random": 4391597250420769051 + }, + "StructPtr": null, + "NoTag": { + "Hello": "person", + "Foo": 0, + "Random": 5000048969203402981 + }, + "NoOption": { + "Hello": "person", + "Foo": 0, + "Random": 4800395242796521920 + } + } + */ }