mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
improve map converting feature using MapOption for package gconv (#3170)
This commit is contained in:
@ -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...)
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 ','.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user