improve map converting feature using MapOption for package gconv (#3170)

This commit is contained in:
John Guo
2023-11-23 18:59:04 +08:00
committed by GitHub
parent ea5d52cb2f
commit 9aa872e705
7 changed files with 139 additions and 50 deletions

View File

@ -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...)
}

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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 ','.

View File

@ -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)

View File

@ -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,
})
})
}