diff --git a/container/gvar/gvar_map.go b/container/gvar/gvar_map.go index 268d9f140..f56874d17 100644 --- a/container/gvar/gvar_map.go +++ b/container/gvar/gvar_map.go @@ -8,24 +8,27 @@ package gvar import "github.com/gogf/gf/v2/util/gconv" +// MapOption specifies the option for map converting. +type MapOption = gconv.MapOption + // Map converts and returns `v` as map[string]interface{}. -func (v *Var) Map(tags ...string) map[string]interface{} { - return gconv.Map(v.Val(), tags...) +func (v *Var) Map(option ...MapOption) map[string]interface{} { + return gconv.Map(v.Val(), option...) } // MapStrAny is like function Map, but implements the interface of MapStrAny. -func (v *Var) MapStrAny() map[string]interface{} { - return v.Map() +func (v *Var) MapStrAny(option ...MapOption) map[string]interface{} { + return v.Map(option...) } // MapStrStr converts and returns `v` as map[string]string. -func (v *Var) MapStrStr(tags ...string) map[string]string { - return gconv.MapStrStr(v.Val(), tags...) +func (v *Var) MapStrStr(option ...MapOption) map[string]string { + return gconv.MapStrStr(v.Val(), option...) } // MapStrVar converts and returns `v` as map[string]Var. -func (v *Var) MapStrVar(tags ...string) map[string]*Var { - m := v.Map(tags...) +func (v *Var) MapStrVar(option ...MapOption) map[string]*Var { + m := v.Map(option...) if len(m) > 0 { vMap := make(map[string]*Var, len(m)) for k, v := range m { @@ -37,16 +40,19 @@ func (v *Var) MapStrVar(tags ...string) map[string]*Var { } // MapDeep converts and returns `v` as map[string]interface{} recursively. +// Deprecated: used Map instead. func (v *Var) MapDeep(tags ...string) map[string]interface{} { return gconv.MapDeep(v.Val(), tags...) } // MapStrStrDeep converts and returns `v` as map[string]string recursively. +// Deprecated: used MapStrStr instead. func (v *Var) MapStrStrDeep(tags ...string) map[string]string { return gconv.MapStrStrDeep(v.Val(), tags...) } // MapStrVarDeep converts and returns `v` as map[string]*Var recursively. +// Deprecated: used MapStrVar instead. func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var { m := v.MapDeep(tags...) if len(m) > 0 { @@ -61,12 +67,12 @@ func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var { // Maps converts and returns `v` as map[string]string. // See gconv.Maps. -func (v *Var) Maps(tags ...string) []map[string]interface{} { - return gconv.Maps(v.Val(), tags...) +func (v *Var) Maps(option ...MapOption) []map[string]interface{} { + return gconv.Maps(v.Val(), option...) } // MapsDeep converts `value` to []map[string]interface{} recursively. -// See gconv.MapsDeep. +// Deprecated: used Maps instead. func (v *Var) MapsDeep(tags ...string) []map[string]interface{} { return gconv.MapsDeep(v.Val(), tags...) } diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 28710a203..ec5850902 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -210,14 +210,14 @@ func GetInsertOperationByOption(option InsertOption) string { } func anyValueToMapBeforeToRecord(value interface{}) map[string]interface{} { - return gconv.Map(value, structTagPriority...) + return gconv.Map(value, gconv.MapOption{Tags: structTagPriority}) } // DataToMapDeep converts `value` to map type recursively(if attribute struct is embedded). // The parameter `value` should be type of *map/map/*struct/struct. // It supports embedded struct definition for struct. func DataToMapDeep(value interface{}) map[string]interface{} { - m := gconv.Map(value, structTagPriority...) + m := gconv.Map(value, gconv.MapOption{Tags: structTagPriority}) for k, v := range m { switch v.(type) { case time.Time, *time.Time, gtime.Time, *gtime.Time, gjson.Json, *gjson.Json: diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index f9a510eec..7627d6840 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -22,40 +22,57 @@ const ( recursiveTypeTrue recursiveType = "true" ) +// MapOption specifies the option for map converting. +type MapOption struct { + // Deep marks doing Map function recursively, which means if the attribute of given converting value + // is also a struct/*struct, it automatically calls Map function on this attribute converting it to + // a map[string]interface{} type variable. + Deep bool + + // OmitEmpty ignores the attributes that has json omitempty tag. + OmitEmpty bool + + // Tags specifies the converted map key name by struct tag name. + Tags []string +} + // Map converts any variable `value` to map[string]interface{}. If the parameter `value` is not a // map/struct/*struct type, then the conversion will fail and returns nil. // // If `value` is a struct/*struct object, the second parameter `tags` specifies the most priority // tags that will be detected, otherwise it detects the tags in order of: // gconv, json, field name. -func Map(value interface{}, tags ...string) map[string]interface{} { - return doMapConvert(value, recursiveTypeAuto, false, tags...) +func Map(value interface{}, option ...MapOption) map[string]interface{} { + return doMapConvert(value, recursiveTypeAuto, false, option...) } // MapDeep does Map function recursively, which means if the attribute of `value` // is also a struct/*struct, calls Map function on this attribute converting it to // a map[string]interface{} type variable. -// Also see Map. +// Deprecated: used Map instead. func MapDeep(value interface{}, tags ...string) map[string]interface{} { - return doMapConvert(value, recursiveTypeTrue, false, tags...) + return doMapConvert(value, recursiveTypeTrue, false, MapOption{ + Tags: tags, + }) } // doMapConvert implements the map converting. // It automatically checks and converts json string to map if `value` is string/[]byte. // // TODO completely implement the recursive converting for all types, especially the map. -func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool, tags ...string) map[string]interface{} { +func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool, option ...MapOption) map[string]interface{} { if value == nil { return nil } + var usedOption = getUsedMapOption(option...) newTags := StructTagPriority - switch len(tags) { + switch len(usedOption.Tags) { case 0: // No need handling. case 1: - newTags = append(strings.Split(tags[0], ","), StructTagPriority...) + newTags = append(strings.Split(usedOption.Tags[0], ","), StructTagPriority...) default: - newTags = append(tags, StructTagPriority...) + newTags = append(usedOption.Tags, StructTagPriority...) } // Assert the common combination of types, and finally it uses reflection. dataMap := make(map[string]interface{}) @@ -79,6 +96,8 @@ func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool return nil } case map[interface{}]interface{}: + recursiveOption := usedOption + recursiveOption.Tags = newTags for k, v := range r { dataMap[String(k)] = doMapConvertForMapOrStructValue( doMapConvertForMapOrStructValueInput{ @@ -86,7 +105,7 @@ func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool Value: v, RecursiveType: recursive, RecursiveOption: recursive == recursiveTypeTrue, - Tags: newTags, + Option: recursiveOption, }, ) } @@ -136,6 +155,8 @@ func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool } case map[string]interface{}: if recursive == recursiveTypeTrue { + recursiveOption := usedOption + recursiveOption.Tags = newTags // A copy of current map. for k, v := range r { dataMap[k] = doMapConvertForMapOrStructValue( @@ -144,7 +165,7 @@ func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool Value: v, RecursiveType: recursive, RecursiveOption: recursive == recursiveTypeTrue, - Tags: newTags, + Option: recursiveOption, }, ) } @@ -153,6 +174,8 @@ func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool return r } case map[int]interface{}: + recursiveOption := usedOption + recursiveOption.Tags = newTags for k, v := range r { dataMap[String(k)] = doMapConvertForMapOrStructValue( doMapConvertForMapOrStructValueInput{ @@ -160,7 +183,7 @@ func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool Value: v, RecursiveType: recursive, RecursiveOption: recursive == recursiveTypeTrue, - Tags: newTags, + Option: recursiveOption, }, ) } @@ -202,13 +225,15 @@ func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool } } case reflect.Map, reflect.Struct, reflect.Interface: + recursiveOption := usedOption + recursiveOption.Tags = newTags convertedValue := doMapConvertForMapOrStructValue( doMapConvertForMapOrStructValueInput{ IsRoot: true, Value: value, RecursiveType: recursive, RecursiveOption: recursive == recursiveTypeTrue, - Tags: newTags, + Option: recursiveOption, MustMapReturn: mustMapReturn, }, ) @@ -223,12 +248,20 @@ func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool return dataMap } +func getUsedMapOption(option ...MapOption) MapOption { + var usedOption MapOption + if len(option) > 0 { + usedOption = option[0] + } + return usedOption +} + type doMapConvertForMapOrStructValueInput struct { IsRoot bool // It returns directly if it is not root and with no recursive converting. Value interface{} // Current operation value. RecursiveType recursiveType // The type from top function entry. RecursiveOption bool // Whether convert recursively for `current` operation. - Tags []string // Map key mapping. + Option MapOption // Map converting option. MustMapReturn bool // Must return map instead of Value when empty. } @@ -280,7 +313,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in Value: mapValue, RecursiveType: in.RecursiveType, RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Tags: in.Tags, + Option: in.Option, }, ) } @@ -299,7 +332,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in Value: mapV, RecursiveType: in.RecursiveType, RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Tags: in.Tags, + Option: in.Option, }, ) } else { @@ -327,7 +360,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in } mapKey = "" fieldTag := rtField.Tag - for _, tag := range in.Tags { + for _, tag := range in.Option.Tags { if mapKey = fieldTag.Get(tag); mapKey != "" { break } @@ -344,7 +377,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in if len(array) > 1 { switch strings.TrimSpace(array[1]) { case "omitempty": - if empty.IsEmpty(rvField.Interface()) { + if in.Option.OmitEmpty && empty.IsEmpty(rvField.Interface()) { continue } else { mapKey = strings.TrimSpace(array[0]) @@ -389,7 +422,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in Value: rvInterface, RecursiveType: in.RecursiveType, RecursiveOption: true, - Tags: in.Tags, + Option: in.Option, }) if m, ok := anonymousValue.(map[string]interface{}); ok { for k, v := range m { @@ -406,7 +439,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in Value: rvInterface, RecursiveType: in.RecursiveType, RecursiveOption: true, - Tags: in.Tags, + Option: in.Option, }) default: @@ -415,7 +448,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in Value: rvInterface, RecursiveType: in.RecursiveType, RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Tags: in.Tags, + Option: in.Option, }) } @@ -434,7 +467,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in Value: rvAttrField.Index(arrayIndex).Interface(), RecursiveType: in.RecursiveType, RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Tags: in.Tags, + Option: in.Option, }, ) } @@ -451,7 +484,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in Value: rvAttrField.MapIndex(k).Interface(), RecursiveType: in.RecursiveType, RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Tags: in.Tags, + Option: in.Option, }, ) } @@ -490,7 +523,7 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in Value: reflectValue.Index(i).Interface(), RecursiveType: in.RecursiveType, RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Tags: in.Tags, + Option: in.Option, }) } return array @@ -500,11 +533,11 @@ func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) in // MapStrStr converts `value` to map[string]string. // Note that there might be data copy for this map type converting. -func MapStrStr(value interface{}, tags ...string) map[string]string { +func MapStrStr(value interface{}, option ...MapOption) map[string]string { if r, ok := value.(map[string]string); ok { return r } - m := Map(value, tags...) + m := Map(value, option...) if len(m) > 0 { vMap := make(map[string]string, len(m)) for k, v := range m { @@ -517,6 +550,7 @@ func MapStrStr(value interface{}, tags ...string) map[string]string { // MapStrStrDeep converts `value` to map[string]string recursively. // Note that there might be data copy for this map type converting. +// Deprecated: used MapStrStr instead. func MapStrStrDeep(value interface{}, tags ...string) map[string]string { if r, ok := value.(map[string]string); ok { return r diff --git a/util/gconv/gconv_maps.go b/util/gconv/gconv_maps.go index 3efe2386f..a68bb4e10 100644 --- a/util/gconv/gconv_maps.go +++ b/util/gconv/gconv_maps.go @@ -9,23 +9,19 @@ package gconv import "github.com/gogf/gf/v2/internal/json" // SliceMap is alias of Maps. -func SliceMap(any interface{}) []map[string]interface{} { - return Maps(any) +func SliceMap(any interface{}, option ...MapOption) []map[string]interface{} { + return Maps(any, option...) } // SliceMapDeep is alias of MapsDeep. +// Deprecated: used SliceMap instead. func SliceMapDeep(any interface{}) []map[string]interface{} { return MapsDeep(any) } -// SliceStruct is alias of Structs. -func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - return Structs(params, pointer, mapping...) -} - // Maps converts `value` to []map[string]interface{}. // Note that it automatically checks and converts json string to []map if `value` is string/[]byte. -func Maps(value interface{}, tags ...string) []map[string]interface{} { +func Maps(value interface{}, option ...MapOption) []map[string]interface{} { if value == nil { return nil } @@ -62,7 +58,7 @@ func Maps(value interface{}, tags ...string) []map[string]interface{} { } list := make([]map[string]interface{}, len(array)) for k, v := range array { - list[k] = Map(v, tags...) + list[k] = Map(v, option...) } return list } @@ -71,6 +67,7 @@ func Maps(value interface{}, tags ...string) []map[string]interface{} { // MapsDeep converts `value` to []map[string]interface{} recursively. // // TODO completely implement the recursive converting for all types. +// Deprecated: used Maps instead. func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { if value == nil { return nil diff --git a/util/gconv/gconv_structs.go b/util/gconv/gconv_structs.go index 42ec26c3f..f8e93656c 100644 --- a/util/gconv/gconv_structs.go +++ b/util/gconv/gconv_structs.go @@ -20,6 +20,11 @@ func Structs(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[s return Scan(params, pointer, paramKeyToAttrMap...) } +// SliceStruct is alias of Structs. +func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { + return Structs(params, pointer, mapping...) +} + // StructsTag acts as Structs but also with support for priority tag feature, which retrieves the // specified tags for `params` key-value items to struct attribute names mapping. // The parameter `priorityTag` supports multiple tags that can be joined with char ','. diff --git a/util/gconv/gconv_z_unit_all_test.go b/util/gconv/gconv_z_unit_all_test.go index d6bc83e0f..78d62c054 100644 --- a/util/gconv/gconv_z_unit_all_test.go +++ b/util/gconv/gconv_z_unit_all_test.go @@ -946,8 +946,8 @@ func Test_Map_StructWithJsonTag_All(t *testing.T) { ssa: "222", } user2 := &user1 - _ = gconv.Map(user1, "Ss") - map1 := gconv.Map(user1, "json", "json2") + _ = gconv.Map(user1, gconv.MapOption{Tags: []string{"Ss"}}) + map1 := gconv.Map(user1, gconv.MapOption{Tags: []string{"json", "json2"}}) map2 := gconv.Map(user2) map3 := gconv.Map(user3) t.Assert(map1["Uid"], 100) diff --git a/util/gconv/gconv_z_unit_map_test.go b/util/gconv/gconv_z_unit_map_test.go index 9dca4ce39..b72a2eac4 100644 --- a/util/gconv/gconv_z_unit_map_test.go +++ b/util/gconv/gconv_z_unit_map_test.go @@ -617,6 +617,53 @@ func TestMapWithJsonOmitEmpty(t *testing.T) { Key: "", Value: 1, } - t.Assert(gconv.Map(s), g.Map{"Value": 1}) + m1 := gconv.Map(s) + t.Assert(m1, g.Map{ + "Key": "", + "Value": 1, + }) + + m2 := gconv.Map(s, gconv.MapOption{ + Deep: false, + OmitEmpty: true, + Tags: nil, + }) + t.Assert(m2, g.Map{ + "Value": 1, + }) + }) + + gtest.C(t, func(t *gtest.T) { + type ProductConfig struct { + Pid int `v:"required" json:"pid,omitempty"` + TimeSpan int `v:"required" json:"timeSpan,omitempty"` + } + type CreateGoodsDetail struct { + ProductConfig + AutoRenewFlag int `v:"required" json:"autoRenewFlag"` + } + s := &CreateGoodsDetail{ + ProductConfig: ProductConfig{ + Pid: 1, + TimeSpan: 0, + }, + AutoRenewFlag: 0, + } + m1 := gconv.Map(s) + t.Assert(m1, g.Map{ + "pid": 1, + "timeSpan": 0, + "autoRenewFlag": 0, + }) + + m2 := gconv.Map(s, gconv.MapOption{ + Deep: false, + OmitEmpty: true, + Tags: nil, + }) + t.Assert(m2, g.Map{ + "pid": 1, + "autoRenewFlag": 0, + }) }) }