Initial QSfera import
This commit is contained in:
+72
@@ -0,0 +1,72 @@
|
||||
# Map Utils
|
||||
|
||||
`maputil` provide map data util functions. eg: convert, sub-value get, simple merge
|
||||
|
||||
- use `map[string]any` as Data
|
||||
- deep get value by key path
|
||||
- deep set value by key path
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/maputil
|
||||
```
|
||||
|
||||
## Go docs
|
||||
|
||||
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/maputil)
|
||||
|
||||
## Usage
|
||||
|
||||
### Deep get value
|
||||
|
||||
```go
|
||||
mp := map[string]any {
|
||||
"top1": "val1",
|
||||
"arr1": []string{"ab", "cd"}
|
||||
"map1": map[string]any{
|
||||
"sub1": "val2",
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Println(maputil.DeepGet(mp, "map1.sub1")) // Output: VAL3
|
||||
|
||||
// get value from slice.
|
||||
fmt.Println(maputil.DeepGet(mp, "arr1.1")) // Output: cd
|
||||
fmt.Println(maputil.DeepGet(mp, "arr1[1]")) // Output: cd
|
||||
```
|
||||
|
||||
### Deep set value
|
||||
|
||||
```go
|
||||
mp := map[string]any {
|
||||
"top1": "val1",
|
||||
"arr1": []string{"ab"}
|
||||
"map1": map[string]any{
|
||||
"sub1": "val2",
|
||||
},
|
||||
}
|
||||
|
||||
err := maputil.SetByPath(&mp, "map1.newKey", "VAL3")
|
||||
|
||||
fmt.Println(maputil.DeepGet(mp, "map1.newKey")) // Output: VAL3
|
||||
```
|
||||
|
||||
## Code Check & Testing
|
||||
|
||||
```bash
|
||||
gofmt -w -l ./
|
||||
golint ./...
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
|
||||
```shell
|
||||
go test -v ./maputil/...
|
||||
```
|
||||
|
||||
**Test limit by regexp**:
|
||||
|
||||
```shell
|
||||
go test -v -run ^TestSetByKeys ./maputil/...
|
||||
```
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Aliases implemented a simple string alias map.
|
||||
// - key: alias, value: real name
|
||||
type Aliases map[string]string
|
||||
|
||||
// AddAlias to the Aliases map
|
||||
func (as Aliases) AddAlias(alias, real string) {
|
||||
if rn, ok := as[alias]; ok {
|
||||
panic(fmt.Sprintf("The alias '%s' is already used by '%s'", alias, rn))
|
||||
}
|
||||
as[alias] = real
|
||||
}
|
||||
|
||||
// AddAliases to the Aliases map
|
||||
func (as Aliases) AddAliases(real string, aliases []string) {
|
||||
for _, a := range aliases {
|
||||
as.AddAlias(a, real)
|
||||
}
|
||||
}
|
||||
|
||||
// AddAliasMap to the Aliases map
|
||||
func (as Aliases) AddAliasMap(alias2real map[string]string) {
|
||||
for a, r := range alias2real {
|
||||
as.AddAlias(a, r)
|
||||
}
|
||||
}
|
||||
|
||||
// HasAlias in the Aliases map
|
||||
func (as Aliases) HasAlias(alias string) bool {
|
||||
if _, ok := as[alias]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ResolveAlias by given name.
|
||||
func (as Aliases) ResolveAlias(alias string) string {
|
||||
if name, ok := as[alias]; ok {
|
||||
return name
|
||||
}
|
||||
return alias
|
||||
}
|
||||
|
||||
// AliasesNames returns all sorted alias names.
|
||||
func (as Aliases) AliasesNames() []string {
|
||||
ns := make([]string, 0, len(as))
|
||||
for alias := range as {
|
||||
ns = append(ns, alias)
|
||||
}
|
||||
sort.Strings(ns)
|
||||
return ns
|
||||
}
|
||||
|
||||
// GroupAliases groups aliases by real name.
|
||||
//
|
||||
// returns: {real name -> []aliases, ...}
|
||||
func (as Aliases) GroupAliases() map[string][]string {
|
||||
gaMap := make(map[string][]string)
|
||||
for alias, name := range as {
|
||||
gaMap[name] = append(gaMap[name], alias)
|
||||
}
|
||||
return gaMap
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gookit/goutil/reflects"
|
||||
)
|
||||
|
||||
// HasKey check of the given map.
|
||||
func HasKey(mp, key any) (ok bool) {
|
||||
rftVal := reflect.Indirect(reflect.ValueOf(mp))
|
||||
if rftVal.Kind() != reflect.Map {
|
||||
return
|
||||
}
|
||||
|
||||
for _, keyRv := range rftVal.MapKeys() {
|
||||
if reflects.IsEqual(keyRv.Interface(), key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasOneKey check of the given map. return the first exist key
|
||||
func HasOneKey(mp any, keys ...any) (ok bool, key any) {
|
||||
rftVal := reflect.Indirect(reflect.ValueOf(mp))
|
||||
if rftVal.Kind() != reflect.Map {
|
||||
return
|
||||
}
|
||||
|
||||
for _, key = range keys {
|
||||
for _, keyRv := range rftVal.MapKeys() {
|
||||
if reflects.IsEqual(keyRv.Interface(), key) {
|
||||
return true, key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// HasAllKeys check of the given map. return the first not exist key
|
||||
func HasAllKeys(mp any, keys ...any) (ok bool, noKey any) {
|
||||
rftVal := reflect.Indirect(reflect.ValueOf(mp))
|
||||
if rftVal.Kind() != reflect.Map {
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
var exist bool
|
||||
for _, keyRv := range rftVal.MapKeys() {
|
||||
if reflects.IsEqual(keyRv.Interface(), key) {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !exist {
|
||||
return false, key
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/comdef"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// alias functions
|
||||
var (
|
||||
// ToStrMap convert map[string]any to map[string]string
|
||||
ToStrMap = ToStringMap
|
||||
// ToL2StrMap convert map[string]any to map[string]map[string]string
|
||||
ToL2StrMap = ToL2StringMap
|
||||
)
|
||||
|
||||
// KeyToLower convert keys to lower case.
|
||||
func KeyToLower(src map[string]string) map[string]string {
|
||||
if len(src) == 0 {
|
||||
return src
|
||||
}
|
||||
|
||||
newMp := make(map[string]string, len(src))
|
||||
for k, v := range src {
|
||||
k = strings.ToLower(k)
|
||||
newMp[k] = v
|
||||
}
|
||||
return newMp
|
||||
}
|
||||
|
||||
// AnyToStrMap try convert any(map[string]any, map[string]string) to map[string]string
|
||||
func AnyToStrMap(src any) map[string]string {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m, ok := src.(map[string]string); ok {
|
||||
return m
|
||||
}
|
||||
if m, ok := src.(map[string]any); ok {
|
||||
return ToStringMap(m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToStringMap simple convert map[string]any to map[string]string
|
||||
func ToStringMap(src map[string]any) map[string]string {
|
||||
strMp := make(map[string]string, len(src))
|
||||
for k, v := range src {
|
||||
strMp[k] = strutil.SafeString(v)
|
||||
}
|
||||
return strMp
|
||||
}
|
||||
|
||||
// ToL2StringMap convert map[string]any to map[string]map[string]string
|
||||
func ToL2StringMap(groupsMap map[string]any) map[string]map[string]string {
|
||||
if len(groupsMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
l2sMap := make(map[string]map[string]string, len(groupsMap))
|
||||
|
||||
for k, v := range groupsMap {
|
||||
if mp, ok := v.(map[string]any); ok {
|
||||
l2sMap[k] = ToStringMap(mp)
|
||||
} else if smp, ok := v.(map[string]string); ok {
|
||||
l2sMap[k] = smp
|
||||
}
|
||||
}
|
||||
return l2sMap
|
||||
}
|
||||
|
||||
// CombineToSMap combine two string-slices to SMap(map[string]string)
|
||||
func CombineToSMap(keys, values []string) SMap {
|
||||
return arrutil.CombineToSMap(keys, values)
|
||||
}
|
||||
|
||||
// CombineToMap combine two any slice to map[K]V. alias of arrutil.CombineToMap
|
||||
func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V {
|
||||
return arrutil.CombineToMap(keys, values)
|
||||
}
|
||||
|
||||
// SliceToSMap convert string k-v pairs slice to map[string]string
|
||||
// - eg: []string{k1,v1,k2,v2} -> map[string]string{k1:v1, k2:v2}
|
||||
func SliceToSMap(kvPairs ...string) map[string]string {
|
||||
ln := len(kvPairs)
|
||||
// check kvPairs length must be even
|
||||
if ln == 0 || ln%2 != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sMap := make(map[string]string, ln/2)
|
||||
for i := 0; i < ln; i += 2 {
|
||||
sMap[kvPairs[i]] = kvPairs[i+1]
|
||||
}
|
||||
return sMap
|
||||
}
|
||||
|
||||
// SliceToMap convert any k-v pairs slice to map[string]any
|
||||
func SliceToMap(kvPairs ...any) map[string]any {
|
||||
ln := len(kvPairs)
|
||||
// check kvPairs length must be even
|
||||
if ln == 0 || ln%2 != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mp := make(map[string]any, ln/2)
|
||||
for i := 0; i < ln; i += 2 {
|
||||
kStr := strutil.SafeString(kvPairs[i])
|
||||
mp[kStr] = kvPairs[i+1]
|
||||
}
|
||||
return mp
|
||||
}
|
||||
|
||||
// SliceToTypeMap convert k-v pairs slice to map[string]T
|
||||
func SliceToTypeMap[T any](valFunc func(any) T, kvPairs ...any) map[string]T {
|
||||
ln := len(kvPairs)
|
||||
// check kvPairs length must be even
|
||||
if ln == 0 || ln%2 != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mp := make(map[string]T, ln/2)
|
||||
for i := 0; i < ln; i += 2 {
|
||||
kStr := strutil.SafeString(kvPairs[i])
|
||||
mp[kStr] = valFunc(kvPairs[i+1])
|
||||
}
|
||||
return mp
|
||||
}
|
||||
|
||||
// ToAnyMap convert map[TYPE1]TYPE2 to map[string]any
|
||||
func ToAnyMap(mp any) map[string]any {
|
||||
amp, _ := TryAnyMap(mp)
|
||||
return amp
|
||||
}
|
||||
|
||||
// TryAnyMap convert map[TYPE1]TYPE2 to map[string]any
|
||||
func TryAnyMap(mp any) (map[string]any, error) {
|
||||
if aMp, ok := mp.(map[string]any); ok {
|
||||
return aMp, nil
|
||||
}
|
||||
if sMp, ok := mp.(map[string]string); ok {
|
||||
anyMp := make(map[string]any, len(sMp))
|
||||
for k, v := range sMp {
|
||||
anyMp[k] = v
|
||||
}
|
||||
return anyMp, nil
|
||||
}
|
||||
|
||||
rv := reflect.Indirect(reflect.ValueOf(mp))
|
||||
if rv.Kind() != reflect.Map {
|
||||
return nil, errors.New("input is not a map value type")
|
||||
}
|
||||
|
||||
anyMp := make(map[string]any, rv.Len())
|
||||
for _, key := range rv.MapKeys() {
|
||||
keyStr := strutil.SafeString(key.Interface())
|
||||
anyMp[keyStr] = rv.MapIndex(key).Interface()
|
||||
}
|
||||
return anyMp, nil
|
||||
}
|
||||
|
||||
// HTTPQueryString convert map[string]any data to http query string.
|
||||
func HTTPQueryString(data map[string]any) string {
|
||||
ss := make([]string, 0, len(data))
|
||||
for k, v := range data {
|
||||
ss = append(ss, k+"="+strutil.QuietString(v))
|
||||
}
|
||||
|
||||
return strings.Join(ss, "&")
|
||||
}
|
||||
|
||||
// StringsMapToAnyMap convert map[string][]string to map[string]any
|
||||
//
|
||||
// Example:
|
||||
// {"k1": []string{"v1", "v2"}, "k2": []string{"v3"}}
|
||||
// =>
|
||||
// {"k": []string{"v1", "v2"}, "k2": "v3"}
|
||||
//
|
||||
// mp := StringsMapToAnyMap(httpReq.Header)
|
||||
func StringsMapToAnyMap(ssMp map[string][]string) map[string]any {
|
||||
if len(ssMp) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
anyMp := make(map[string]any, len(ssMp))
|
||||
for k, v := range ssMp {
|
||||
if len(v) == 1 {
|
||||
anyMp[k] = v[0]
|
||||
continue
|
||||
}
|
||||
anyMp[k] = v
|
||||
}
|
||||
return anyMp
|
||||
}
|
||||
|
||||
// ToString simple and quickly convert map[string]any to string.
|
||||
func ToString(mp map[string]any) string {
|
||||
if mp == nil {
|
||||
return ""
|
||||
}
|
||||
if len(mp) == 0 {
|
||||
return "{}"
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, len(mp)*16)
|
||||
buf = append(buf, '{')
|
||||
|
||||
for k, val := range mp {
|
||||
buf = append(buf, k...)
|
||||
buf = append(buf, ':')
|
||||
|
||||
str := strutil.QuietString(val)
|
||||
buf = append(buf, str...)
|
||||
buf = append(buf, ',', ' ')
|
||||
}
|
||||
|
||||
// remove last ', '
|
||||
buf = append(buf[:len(buf)-2], '}')
|
||||
return strutil.Byte2str(buf)
|
||||
}
|
||||
|
||||
// ToString2 simple and quickly convert a map to string.
|
||||
func ToString2(mp any) string { return NewFormatter(mp).Format() }
|
||||
|
||||
// FormatIndent format map data to string with newline and indent.
|
||||
func FormatIndent(mp any, indent string) string {
|
||||
return NewFormatter(mp).WithIndent(indent).Format()
|
||||
}
|
||||
|
||||
// StrMapToText 将 map[string]string 转换为多行 key=value 格式文本
|
||||
func StrMapToText(m map[string]string) string {
|
||||
if len(m) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var lines []string
|
||||
for key, value := range m {
|
||||
lines = append(lines, key+"="+value)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Flat convert tree map to flatten key-value map.
|
||||
*************************************************************/
|
||||
|
||||
// Flatten convert tree map to flat key-value map.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// {"top": {"sub": "value", "sub2": "value2"} }
|
||||
// ->
|
||||
// {"top.sub": "value", "top.sub2": "value2" }
|
||||
func Flatten(mp map[string]any) map[string]any {
|
||||
if mp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
flatMp := make(map[string]any, len(mp)*2)
|
||||
reflects.FlatMap(reflect.ValueOf(mp), func(path string, val reflect.Value) {
|
||||
flatMp[path] = val.Interface()
|
||||
})
|
||||
|
||||
return flatMp
|
||||
}
|
||||
|
||||
// FlatWithFunc flat a tree-map with custom collect handle func
|
||||
func FlatWithFunc(mp map[string]any, fn reflects.FlatFunc) {
|
||||
if mp == nil || fn == nil {
|
||||
return
|
||||
}
|
||||
reflects.FlatMap(reflect.ValueOf(mp), fn)
|
||||
}
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// Map alias of Data
|
||||
type Map = Data
|
||||
|
||||
// Data alias of map[string]any
|
||||
type Data map[string]any
|
||||
|
||||
// Has value on the data map
|
||||
func (d Data) Has(key string) bool {
|
||||
_, ok := d.GetByPath(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsEmpty if the data map
|
||||
func (d Data) IsEmpty() bool {
|
||||
return len(d) == 0
|
||||
}
|
||||
|
||||
//
|
||||
// endregion
|
||||
// region T: set value(s)
|
||||
//
|
||||
|
||||
// Set value to the data map
|
||||
func (d Data) Set(key string, val any) {
|
||||
d[key] = val
|
||||
}
|
||||
|
||||
// SetByPath sets a value in the map.
|
||||
// Supports dot syntax to set deep values.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// d.SetByPath("name.first", "Mat")
|
||||
func (d Data) SetByPath(path string, value any) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
return d.SetByKeys(strings.Split(path, KeySepStr), value)
|
||||
}
|
||||
|
||||
// SetByKeys sets a value in the map by path keys.
|
||||
// Supports dot syntax to set deep values.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// d.SetByKeys([]string{"name", "first"}, "Mat")
|
||||
func (d Data) SetByKeys(keys []string, value any) error {
|
||||
kln := len(keys)
|
||||
if kln == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// special handle d is empty.
|
||||
if len(d) == 0 {
|
||||
if kln == 1 {
|
||||
d.Set(keys[0], value)
|
||||
} else {
|
||||
d.Set(keys[0], MakeByKeys(keys[1:], value))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return SetByKeys((*map[string]any)(&d), keys, value)
|
||||
// It's ok, but use `func (d *Data)`
|
||||
// return SetByKeys((*map[string]any)(d), keys, value)
|
||||
}
|
||||
|
||||
//
|
||||
// endregion
|
||||
// region T: read value(s)
|
||||
//
|
||||
|
||||
// Value get from the data map
|
||||
func (d Data) Value(key string) (any, bool) {
|
||||
val, ok := d.GetByPath(key)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Get value from the data map.
|
||||
// Supports dot syntax to get deep values. eg: top.sub
|
||||
func (d Data) Get(key string) any {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// One get value from the data by multi paths. will return first founded value
|
||||
func (d Data) One(keys ...string) any {
|
||||
if val, ok := d.TryOne(keys...); ok {
|
||||
return val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryOne get value from the data by multi paths. will return first founded value
|
||||
func (d Data) TryOne(keys ...string) (any, bool) {
|
||||
for _, path := range keys {
|
||||
if val, ok := d.GetByPath(path); ok {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetByPath get value from the data map by path. eg: top.sub
|
||||
// Supports dot syntax to get deep values.
|
||||
func (d Data) GetByPath(path string) (any, bool) {
|
||||
if val, ok := d[path]; ok {
|
||||
return val, true
|
||||
}
|
||||
|
||||
// is a key path.
|
||||
if strings.ContainsRune(path, '.') {
|
||||
val, ok := GetByPath(path, d)
|
||||
if ok {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Default get value from the data map with default value
|
||||
func (d Data) Default(key string, def any) any {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return val
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// Int value get, or default value
|
||||
func (d Data) Int(key string, defVal ...int) int {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return mathutil.SafeInt(val)
|
||||
}
|
||||
if len(defVal) > 0 {
|
||||
return defVal[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Int64 value get, or default value
|
||||
func (d Data) Int64(key string, defVal ...int64) int64 {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return mathutil.SafeInt64(val)
|
||||
}
|
||||
if len(defVal) > 0 {
|
||||
return defVal[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Uint value get, or default value
|
||||
func (d Data) Uint(key string, defVal ...uint) uint {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return mathutil.QuietUint(val)
|
||||
}
|
||||
if len(defVal) > 0 {
|
||||
return defVal[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Uint16 value get, or default value
|
||||
func (d Data) Uint16(key string, defVal ...uint16) uint16 {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return uint16(mathutil.SafeUint(val))
|
||||
}
|
||||
|
||||
if len(defVal) > 0 {
|
||||
return defVal[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Uint64 value get, or default value
|
||||
func (d Data) Uint64(key string, defVal ...uint64) uint64 {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return mathutil.QuietUint64(val)
|
||||
}
|
||||
if len(defVal) > 0 {
|
||||
return defVal[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Str value gets by key, or default value
|
||||
func (d Data) Str(key string, defVal ...string) string {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return strutil.SafeString(val)
|
||||
}
|
||||
if len(defVal) > 0 {
|
||||
return defVal[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// StrOne value gets by multi keys, will return first value
|
||||
func (d Data) StrOne(keys ...string) string {
|
||||
for _, key := range keys {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return strutil.SafeString(val)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Bool value get
|
||||
func (d Data) Bool(key string) bool {
|
||||
val, ok := d.GetByPath(key)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return comfunc.Bool(val)
|
||||
}
|
||||
|
||||
// BoolOne value gets from multi keys, return first value
|
||||
func (d Data) BoolOne(keys ...string) bool {
|
||||
for _, key := range keys {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return comfunc.Bool(val)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// StringsOne get []string value by multi keys, return first founded value
|
||||
func (d Data) StringsOne(keys ...string) []string {
|
||||
for _, key := range keys {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return arrutil.AnyToStrings(val)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Strings get []string value by key
|
||||
func (d Data) Strings(key string) []string {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return arrutil.AnyToStrings(val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StrSplit get strings by split string value
|
||||
func (d Data) StrSplit(key, sep string) []string {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return strings.Split(strutil.SafeString(val), sep)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringsByStr value gets by key, will split string value by ","
|
||||
func (d Data) StringsByStr(key string) []string {
|
||||
return d.StrSplit(key, ",")
|
||||
}
|
||||
|
||||
// StrMap get map[string]string value
|
||||
func (d Data) StrMap(key string) map[string]string {
|
||||
return d.StringMap(key)
|
||||
}
|
||||
|
||||
// StringMap get map[string]string value
|
||||
func (d Data) StringMap(key string) map[string]string {
|
||||
val, ok := d.GetByPath(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch tv := val.(type) {
|
||||
case map[string]string:
|
||||
return tv
|
||||
case map[string]any:
|
||||
return ToStringMap(tv)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Sub get sub value(map[string]any) as new Data
|
||||
func (d Data) Sub(key string) Data {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return d.toAnyMap(val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnyMap get sub value as map[string]any
|
||||
func (d Data) AnyMap(key string) map[string]any {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return d.toAnyMap(val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnyMap get sub value as map[string]any
|
||||
func (d Data) toAnyMap(val any) map[string]any {
|
||||
switch tv := val.(type) {
|
||||
case map[string]string:
|
||||
return ToAnyMap(tv)
|
||||
case map[string]any:
|
||||
return tv
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Slice get []any value from data map
|
||||
func (d Data) Slice(key string) ([]any, error) {
|
||||
val, ok := d.GetByPath(key)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return arrutil.AnyToSlice(val)
|
||||
}
|
||||
|
||||
// Keys of the data map
|
||||
func (d Data) Keys() []string {
|
||||
keys := make([]string, 0, len(d))
|
||||
for k := range d {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// ToStringMap convert to map[string]string
|
||||
func (d Data) ToStringMap() map[string]string {
|
||||
return ToStringMap(d)
|
||||
}
|
||||
|
||||
// String data to string
|
||||
func (d Data) String() string {
|
||||
return ToString(d)
|
||||
}
|
||||
|
||||
// Load other data to current data map
|
||||
func (d Data) Load(sub map[string]any) {
|
||||
for name, val := range sub {
|
||||
d[name] = val
|
||||
}
|
||||
}
|
||||
|
||||
// LoadSMap to data
|
||||
func (d Data) LoadSMap(smp map[string]string) {
|
||||
for name, val := range smp {
|
||||
d[name] = val
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/gookit/goutil/comdef"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// MapFormatter struct
|
||||
type MapFormatter struct {
|
||||
comdef.BaseFormatter
|
||||
// Prefix string for each element
|
||||
Prefix string
|
||||
// Indent string for each element
|
||||
Indent string
|
||||
// ClosePrefix string for last "}"
|
||||
ClosePrefix string
|
||||
// AfterReset after reset on call Format().
|
||||
// AfterReset bool
|
||||
}
|
||||
|
||||
// NewFormatter instance
|
||||
func NewFormatter(mp any) *MapFormatter {
|
||||
f := &MapFormatter{}
|
||||
f.Src = mp
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// WithFn for config self
|
||||
func (f *MapFormatter) WithFn(fn func(f *MapFormatter)) *MapFormatter {
|
||||
fn(f)
|
||||
return f
|
||||
}
|
||||
|
||||
// WithIndent string
|
||||
func (f *MapFormatter) WithIndent(indent string) *MapFormatter {
|
||||
f.Indent = indent
|
||||
return f
|
||||
}
|
||||
|
||||
// FormatTo to custom buffer
|
||||
func (f *MapFormatter) FormatTo(w io.Writer) {
|
||||
f.SetOutput(w)
|
||||
f.doFormat()
|
||||
}
|
||||
|
||||
// Format to string
|
||||
func (f *MapFormatter) String() string {
|
||||
return f.Format()
|
||||
}
|
||||
|
||||
// Format to string
|
||||
func (f *MapFormatter) Format() string {
|
||||
f.doFormat()
|
||||
return f.BsWriter().String()
|
||||
}
|
||||
|
||||
// Format map data to string.
|
||||
//
|
||||
//goland:noinspection GoUnhandledErrorResult
|
||||
func (f *MapFormatter) doFormat() {
|
||||
if f.Src == nil {
|
||||
return
|
||||
}
|
||||
|
||||
rv, ok := f.Src.(reflect.Value)
|
||||
if !ok {
|
||||
rv = reflect.ValueOf(f.Src)
|
||||
}
|
||||
|
||||
rv = reflect.Indirect(rv)
|
||||
if rv.Kind() != reflect.Map {
|
||||
return
|
||||
}
|
||||
|
||||
buf := f.BsWriter()
|
||||
ln := rv.Len()
|
||||
if ln == 0 {
|
||||
buf.WriteString("{}")
|
||||
return
|
||||
}
|
||||
|
||||
// buf.Grow(ln * 16)
|
||||
buf.WriteByte('{')
|
||||
|
||||
indentLn := len(f.Indent)
|
||||
if indentLn > 0 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
for i, key := range rv.MapKeys() {
|
||||
strK := strutil.SafeString(key.Interface())
|
||||
if indentLn > 0 {
|
||||
buf.WriteString(f.Indent)
|
||||
}
|
||||
|
||||
buf.WriteString(strK)
|
||||
buf.WriteByte(':')
|
||||
|
||||
strV := strutil.SafeString(rv.MapIndex(key).Interface())
|
||||
buf.WriteString(strV)
|
||||
if i < ln-1 {
|
||||
buf.WriteByte(',')
|
||||
|
||||
// no indent, with space
|
||||
if indentLn == 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
|
||||
// with newline
|
||||
if indentLn > 0 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
if f.ClosePrefix != "" {
|
||||
buf.WriteString(f.ClosePrefix)
|
||||
}
|
||||
|
||||
buf.WriteByte('}')
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/comdef"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
)
|
||||
|
||||
// some consts for separators
|
||||
const (
|
||||
Wildcard = "*"
|
||||
PathSep = "."
|
||||
)
|
||||
|
||||
// DeepGet value by key path. eg "top" "top.sub"
|
||||
func DeepGet(mp map[string]any, path string) (val any) {
|
||||
val, _ = GetByPath(path, mp)
|
||||
return
|
||||
}
|
||||
|
||||
// QuietGet value by key path. eg "top" "top.sub"
|
||||
func QuietGet(mp map[string]any, path string) (val any) {
|
||||
val, _ = GetByPath(path, mp)
|
||||
return
|
||||
}
|
||||
|
||||
// GetFromAny get value by key path from any(map,slice) data. eg "top" "top.sub"
|
||||
func GetFromAny(path string, data any) (val any, ok bool) {
|
||||
// empty data
|
||||
if data == nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return data, true
|
||||
}
|
||||
|
||||
return getByPathKeys(data, strings.Split(path, "."))
|
||||
}
|
||||
|
||||
// GetByPath get value by key path from a map(map[string]any). eg "top" "top.sub"
|
||||
func GetByPath(path string, mp map[string]any) (val any, ok bool) {
|
||||
if len(path) == 0 {
|
||||
return mp, true
|
||||
}
|
||||
if val, ok := mp[path]; ok {
|
||||
return val, true
|
||||
}
|
||||
|
||||
// no sub key
|
||||
if len(mp) == 0 || strings.IndexByte(path, '.') < 1 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// key is path. eg: "top.sub"
|
||||
return GetByPathKeys(mp, strings.Split(path, "."))
|
||||
}
|
||||
|
||||
// GetByPathKeys get value by path keys from a map(map[string]any). eg "top" "top.sub"
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// mp := map[string]any{
|
||||
// "top": map[string]any{
|
||||
// "sub": "value",
|
||||
// },
|
||||
// }
|
||||
// val, ok := GetByPathKeys(mp, []string{"top", "sub"}) // return "value", true
|
||||
func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
|
||||
kl := len(keys)
|
||||
if kl == 0 {
|
||||
return mp, true
|
||||
}
|
||||
|
||||
// find top item data use top key
|
||||
var item any
|
||||
topK := keys[0]
|
||||
if item, ok = mp[topK]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// find sub item data use sub key
|
||||
return getByPathKeys(item, keys[1:])
|
||||
}
|
||||
|
||||
func getByPathKeys(item any, keys []string) (val any, ok bool) {
|
||||
kl := len(keys)
|
||||
|
||||
for i, k := range keys {
|
||||
switch tData := item.(type) {
|
||||
case map[string]string: // is string map
|
||||
if item, ok = tData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[string]any: // is map(decode from toml/json/yaml)
|
||||
if item, ok = tData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[any]any: // is map(decode from yaml.v2)
|
||||
if item, ok = tData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case []map[string]any: // is an any-map slice
|
||||
if k == Wildcard {
|
||||
if kl == i+1 { // * is last key
|
||||
return tData, true
|
||||
}
|
||||
|
||||
// * is not last key, find sub item data
|
||||
sl := make([]any, 0, len(tData))
|
||||
for _, v := range tData {
|
||||
if val, ok = getByPathKeys(v, keys[i+1:]); ok {
|
||||
sl = append(sl, val)
|
||||
}
|
||||
}
|
||||
|
||||
if len(sl) > 0 {
|
||||
return sl, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// k is index number
|
||||
idx, err := strconv.Atoi(k)
|
||||
if err != nil || idx >= len(tData) {
|
||||
return nil, false
|
||||
}
|
||||
item = tData[idx]
|
||||
default:
|
||||
if k == Wildcard && kl == i+1 { // * is last key
|
||||
return tData, true
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(tData)
|
||||
// check is slice
|
||||
if rv.Kind() == reflect.Slice {
|
||||
if k == Wildcard {
|
||||
// * is not last key, find sub item data
|
||||
sl := make([]any, 0, rv.Len())
|
||||
for si := 0; si < rv.Len(); si++ {
|
||||
el := reflects.Indirect(rv.Index(si))
|
||||
if el.Kind() != reflect.Map {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// el is map value.
|
||||
if val, ok = getByPathKeys(el.Interface(), keys[i+1:]); ok {
|
||||
sl = append(sl, val)
|
||||
}
|
||||
}
|
||||
|
||||
if len(sl) > 0 {
|
||||
return sl, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// check k is index number
|
||||
ii, err := strconv.Atoi(k)
|
||||
if err != nil || ii >= rv.Len() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
item = rv.Index(ii).Interface()
|
||||
continue
|
||||
}
|
||||
|
||||
// as error
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// next is last key and it is *
|
||||
if kl == i+2 && keys[i+1] == Wildcard {
|
||||
return item, true
|
||||
}
|
||||
}
|
||||
|
||||
return item, true
|
||||
}
|
||||
|
||||
// Keys get all keys of the given map.
|
||||
func Keys(mp any) (keys []string) {
|
||||
rftVal := reflect.Indirect(reflect.ValueOf(mp))
|
||||
if rftVal.Kind() != reflect.Map {
|
||||
return
|
||||
}
|
||||
|
||||
keys = make([]string, 0, rftVal.Len())
|
||||
for _, key := range rftVal.MapKeys() {
|
||||
keys = append(keys, key.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TypedKeys get all keys of the given typed map.
|
||||
func TypedKeys[K comdef.SimpleType, V any](mp map[K]V) (keys []K) {
|
||||
for key := range mp {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FirstKey returns the first key of the given map.
|
||||
func FirstKey[T any](mp map[string]T) string {
|
||||
for key := range mp {
|
||||
return key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Values get all values from the given map.
|
||||
func Values(mp any) (values []any) {
|
||||
rv := reflect.Indirect(reflect.ValueOf(mp))
|
||||
if rv.Kind() != reflect.Map {
|
||||
return
|
||||
}
|
||||
|
||||
values = make([]any, 0, rv.Len())
|
||||
for _, key := range rv.MapKeys() {
|
||||
values = append(values, rv.MapIndex(key).Interface())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TypedValues get all values from the given typed map.
|
||||
func TypedValues[K comdef.SimpleType, V any](mp map[K]V) (values []V) {
|
||||
for _, val := range mp {
|
||||
values = append(values, val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EachAnyMap iterates the given map and calls the given function for each item.
|
||||
func EachAnyMap(mp any, fn func(key string, val any)) {
|
||||
rv := reflect.Indirect(reflect.ValueOf(mp))
|
||||
if rv.Kind() != reflect.Map {
|
||||
panic("not a map value")
|
||||
}
|
||||
|
||||
for _, key := range rv.MapKeys() {
|
||||
fn(key.String(), rv.MapIndex(key).Interface())
|
||||
}
|
||||
}
|
||||
|
||||
// EachTypedMap iterates the given map and calls the given function for each item.
|
||||
func EachTypedMap[K comdef.SimpleType, V any](mp map[K]V, fn func(key K, val V)) {
|
||||
for key, val := range mp {
|
||||
fn(key, val)
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
// Package maputil provide map data util functions. eg: convert, sub-value get, simple merge
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
)
|
||||
|
||||
// Key, value sep char consts
|
||||
const (
|
||||
ValSepStr = ","
|
||||
ValSepChar = ','
|
||||
KeySepStr = "."
|
||||
KeySepChar = '.'
|
||||
)
|
||||
|
||||
// Copy copies all key/value pairs in src adding them to dst.
|
||||
// When a key in src is already present in dst,
|
||||
// the value in dst will be overwritten by the value associated
|
||||
// with the key in src.
|
||||
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteFunc deletes any key/value pairs from m for which del returns true.
|
||||
func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
|
||||
for k, v := range m {
|
||||
if del(k, v) {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SimpleMerge simple merge two data map by string key. will merge the src to dst map
|
||||
func SimpleMerge(src, dst map[string]any) map[string]any {
|
||||
if len(src) == 0 {
|
||||
return dst
|
||||
}
|
||||
if len(dst) == 0 {
|
||||
return src
|
||||
}
|
||||
|
||||
for key, val := range src {
|
||||
if mp, ok := val.(map[string]any); ok {
|
||||
if dmp, ok := dst[key].(map[string]any); ok {
|
||||
dst[key] = SimpleMerge(mp, dmp)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// simple merge
|
||||
dst[key] = val
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Merge1level merge multi any map[string]any data. only merge one level data.
|
||||
func Merge1level(mps ...map[string]any) map[string]any {
|
||||
newMp := make(map[string]any)
|
||||
for _, mp := range mps {
|
||||
for k, v := range mp {
|
||||
newMp[k] = v
|
||||
}
|
||||
}
|
||||
return newMp
|
||||
}
|
||||
|
||||
// func DeepMerge(src, dst map[string]any, deep int) map[string]any { TODO
|
||||
// }
|
||||
|
||||
// MergeSMap simple merge two string map. merge src to dst map
|
||||
func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string {
|
||||
return MergeStringMap(src, dst, ignoreCase)
|
||||
}
|
||||
|
||||
// MergeStrMap simple merge two string map. merge src to dst map
|
||||
func MergeStrMap(src, dst map[string]string) map[string]string {
|
||||
return MergeStringMap(src, dst, false)
|
||||
}
|
||||
|
||||
// AppendSMap append string map data to dst map.
|
||||
func AppendSMap(dst, src map[string]string) map[string]string {
|
||||
return MergeStringMap(src, dst, false)
|
||||
}
|
||||
|
||||
// MergeStringMap simple merge two string map. merge src to dst map
|
||||
func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string {
|
||||
if len(src) == 0 {
|
||||
return dst
|
||||
}
|
||||
if len(dst) == 0 {
|
||||
return src
|
||||
}
|
||||
|
||||
for k, v := range src {
|
||||
if ignoreCase {
|
||||
k = strings.ToLower(k)
|
||||
}
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// MergeMultiSMap quick merge multi string-map data.
|
||||
func MergeMultiSMap(mps ...map[string]string) map[string]string {
|
||||
newMp := make(map[string]string)
|
||||
for _, mp := range mps {
|
||||
for k, v := range mp {
|
||||
newMp[k] = v
|
||||
}
|
||||
}
|
||||
return newMp
|
||||
}
|
||||
|
||||
// MergeL2StrMap merge multi level2 string-map data. The back map covers the front.
|
||||
func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string {
|
||||
newMp := make(map[string]map[string]string)
|
||||
for _, mp := range mps {
|
||||
for k, v := range mp {
|
||||
// merge level 2 value
|
||||
if oldV, ok := newMp[k]; ok {
|
||||
for k1, v1 := range v {
|
||||
oldV[k1] = v1
|
||||
}
|
||||
newMp[k] = oldV
|
||||
} else {
|
||||
newMp[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return newMp
|
||||
}
|
||||
|
||||
// FilterSMap filter empty elem for the string map.
|
||||
func FilterSMap(sm map[string]string) map[string]string {
|
||||
for key, val := range sm {
|
||||
if val == "" {
|
||||
delete(sm, key)
|
||||
}
|
||||
}
|
||||
return sm
|
||||
}
|
||||
|
||||
// MakeByPath build new value by key names
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// "site.info"
|
||||
// ->
|
||||
// map[string]any {
|
||||
// site: {info: val}
|
||||
// }
|
||||
//
|
||||
// // case 2, last key is slice:
|
||||
// "site.tags[1]"
|
||||
// ->
|
||||
// map[string]any {
|
||||
// site: {tags: [val]}
|
||||
// }
|
||||
func MakeByPath(path string, val any) (mp map[string]any) {
|
||||
return MakeByKeys(strings.Split(path, KeySepStr), val)
|
||||
}
|
||||
|
||||
// MakeByKeys build new value by key names
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // case 1:
|
||||
// []string{"site", "info"}
|
||||
// ->
|
||||
// map[string]any {
|
||||
// site: {info: val}
|
||||
// }
|
||||
//
|
||||
// // case 2, last key is slice:
|
||||
// []string{"site", "tags[1]"}
|
||||
// ->
|
||||
// map[string]any {
|
||||
// site: {tags: [val]}
|
||||
// }
|
||||
func MakeByKeys(keys []string, val any) (mp map[string]any) {
|
||||
size := len(keys)
|
||||
|
||||
// if last key contains slice index, make slice wrap the val
|
||||
lastKey := keys[size-1]
|
||||
if newK, idx, ok := parseArrKeyIndex(lastKey); ok {
|
||||
// valTyp := reflect.TypeOf(val)
|
||||
sliTyp := reflect.SliceOf(reflect.TypeOf(val))
|
||||
sliVal := reflect.MakeSlice(sliTyp, idx+1, idx+1)
|
||||
sliVal.Index(idx).Set(reflect.ValueOf(val))
|
||||
|
||||
// update val and last key
|
||||
val = sliVal.Interface()
|
||||
keys[size-1] = newK
|
||||
}
|
||||
|
||||
if size == 1 {
|
||||
return map[string]any{keys[0]: val}
|
||||
}
|
||||
|
||||
// multi nodes
|
||||
arrutil.Reverse(keys)
|
||||
for _, p := range keys {
|
||||
if mp == nil {
|
||||
mp = map[string]any{p: val}
|
||||
} else {
|
||||
mp = map[string]any{p: mp}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// SetByPath set sub-map value by key path.
|
||||
// Supports dot syntax to set deep values.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// SetByPath("name.first", "Mat")
|
||||
func SetByPath(mp *map[string]any, path string, val any) error {
|
||||
return SetByKeys(mp, strings.Split(path, KeySepStr), val)
|
||||
}
|
||||
|
||||
// SetByKeys set sub-map value by path keys.
|
||||
// Supports dot syntax to set deep values.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// SetByKeys([]string{"name", "first"}, "Mat")
|
||||
func SetByKeys(mp *map[string]any, keys []string, val any) (err error) {
|
||||
kln := len(keys)
|
||||
if kln == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mpv := *mp
|
||||
if len(mpv) == 0 {
|
||||
*mp = MakeByKeys(keys, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
topK := keys[0]
|
||||
if kln == 1 {
|
||||
mpv[topK] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := mpv[topK]; !ok {
|
||||
mpv[topK] = MakeByKeys(keys[1:], val)
|
||||
return nil
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(mp).Elem()
|
||||
return setMapByKeys(rv, keys, reflect.ValueOf(val))
|
||||
}
|
||||
|
||||
func setMapByKeys(rv reflect.Value, keys []string, nv reflect.Value) (err error) {
|
||||
if rv.Kind() != reflect.Map {
|
||||
return fmt.Errorf("input parameter#rv must be a Map, but was %s", rv.Kind())
|
||||
}
|
||||
|
||||
// If the map is nil, make a new map
|
||||
if rv.IsNil() {
|
||||
mapType := reflect.MapOf(rv.Type().Key(), rv.Type().Elem())
|
||||
rv.Set(reflect.MakeMap(mapType))
|
||||
}
|
||||
|
||||
var ok bool
|
||||
maxI := len(keys) - 1
|
||||
for i, key := range keys {
|
||||
idx := -1
|
||||
isMap := rv.Kind() == reflect.Map
|
||||
isSlice := rv.Kind() == reflect.Slice
|
||||
isLast := i == len(keys)-1
|
||||
|
||||
// slice index key must be ended on the keys.
|
||||
// eg: "top.arr[2]" -> "arr[2]"
|
||||
if pos := strings.IndexRune(key, '['); pos > 0 {
|
||||
var realKey string
|
||||
if realKey, idx, ok = parseArrKeyIndex(key); ok {
|
||||
// update value
|
||||
key = realKey
|
||||
if !isMap {
|
||||
err = fmt.Errorf(
|
||||
"current value#%s type is %s, cannot get sub-value by key: %s",
|
||||
strings.Join(keys[i:], "."),
|
||||
rv.Kind(),
|
||||
key,
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
rftK := reflect.ValueOf(key)
|
||||
tmpV := rv.MapIndex(rftK)
|
||||
if !tmpV.IsValid() {
|
||||
if isLast {
|
||||
sliVal := reflect.MakeSlice(reflect.SliceOf(nv.Type()), idx+1, idx+1)
|
||||
sliVal.Index(idx).Set(nv)
|
||||
rv.SetMapIndex(rftK, sliVal)
|
||||
} else {
|
||||
// deep make map by keys
|
||||
newVal := MakeByKeys(keys[i+1:], nv.Interface())
|
||||
mpVal := reflect.ValueOf(newVal)
|
||||
|
||||
sliVal := reflect.MakeSlice(reflect.SliceOf(mpVal.Type()), idx+1, idx+1)
|
||||
sliVal.Index(idx).Set(mpVal)
|
||||
|
||||
rv.SetMapIndex(rftK, sliVal)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// get real type: any -> map
|
||||
if tmpV.Kind() == reflect.Interface {
|
||||
tmpV = tmpV.Elem()
|
||||
}
|
||||
|
||||
if tmpV.Kind() != reflect.Slice {
|
||||
err = fmt.Errorf(
|
||||
"current value#%s type is %s, cannot set sub by index: %d",
|
||||
strings.Join(keys[i:], "."),
|
||||
tmpV.Kind(),
|
||||
idx,
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
wantLen := idx + 1
|
||||
sliLen := tmpV.Len()
|
||||
elemTyp := tmpV.Type().Elem()
|
||||
|
||||
if wantLen > sliLen {
|
||||
newAdd := reflect.MakeSlice(tmpV.Type(), 0, wantLen-sliLen)
|
||||
for i := 0; i < wantLen-sliLen; i++ {
|
||||
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
|
||||
}
|
||||
|
||||
tmpV = reflect.AppendSlice(tmpV, newAdd)
|
||||
}
|
||||
|
||||
if !isLast {
|
||||
if elemTyp.Kind() == reflect.Map {
|
||||
err := setMapByKeys(tmpV.Index(idx), keys[i+1:], nv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// tmpV.Index(idx).Set(elemV)
|
||||
rv.SetMapIndex(rftK, tmpV)
|
||||
} else {
|
||||
err = fmt.Errorf(
|
||||
"key %s[%d] elem must be map for set sub-value by remain path: %s",
|
||||
key,
|
||||
idx,
|
||||
strings.Join(keys[i:], "."),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// last - set value
|
||||
tmpV.Index(idx).Set(nv)
|
||||
rv.SetMapIndex(rftK, tmpV)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// set value on last key
|
||||
if isLast {
|
||||
if isMap {
|
||||
rv.SetMapIndex(reflect.ValueOf(key), nv)
|
||||
break
|
||||
}
|
||||
|
||||
if isSlice {
|
||||
// key is slice index
|
||||
if strutil.IsInt(key) {
|
||||
idx, _ = strconv.Atoi(key)
|
||||
}
|
||||
|
||||
if idx > -1 {
|
||||
wantLen := idx + 1
|
||||
sliLen := rv.Len()
|
||||
|
||||
if wantLen > sliLen {
|
||||
elemTyp := rv.Type().Elem()
|
||||
newAdd := reflect.MakeSlice(rv.Type(), 0, wantLen-sliLen)
|
||||
|
||||
for i := 0; i < wantLen-sliLen; i++ {
|
||||
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
|
||||
}
|
||||
|
||||
if !rv.CanAddr() {
|
||||
err = fmt.Errorf("cannot set value to a cannot addr slice, key: %s", key)
|
||||
break
|
||||
}
|
||||
|
||||
rv.Set(reflect.AppendSlice(rv, newAdd))
|
||||
}
|
||||
|
||||
rv.Index(idx).Set(nv)
|
||||
} else {
|
||||
err = fmt.Errorf("cannot set slice value by named key %q", key)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf(
|
||||
"cannot set sub-value for type %q(path %q, key %q)",
|
||||
rv.Kind(),
|
||||
strings.Join(keys[:i], "."),
|
||||
key,
|
||||
)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if isMap {
|
||||
rftK := reflect.ValueOf(key)
|
||||
if tmpV := rv.MapIndex(rftK); tmpV.IsValid() {
|
||||
var isPtr bool
|
||||
// get real type: any -> map
|
||||
tmpV, isPtr = getRealVal(tmpV)
|
||||
if tmpV.Kind() == reflect.Map {
|
||||
rv = tmpV
|
||||
continue
|
||||
}
|
||||
|
||||
// sub is slice and is not ptr
|
||||
if tmpV.Kind() == reflect.Slice {
|
||||
if isPtr {
|
||||
rv = tmpV
|
||||
continue // to (E)
|
||||
}
|
||||
|
||||
// next key is index number.
|
||||
nxtKey := keys[i+1]
|
||||
if strutil.IsInt(nxtKey) {
|
||||
idx, _ = strconv.Atoi(nxtKey)
|
||||
sliLen := tmpV.Len()
|
||||
wantLen := idx + 1
|
||||
|
||||
if wantLen > sliLen {
|
||||
elemTyp := tmpV.Type().Elem()
|
||||
newAdd := reflect.MakeSlice(tmpV.Type(), 0, wantLen-sliLen)
|
||||
for i := 0; i < wantLen-sliLen; i++ {
|
||||
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
|
||||
}
|
||||
|
||||
tmpV = reflect.AppendSlice(tmpV, newAdd)
|
||||
}
|
||||
|
||||
// rv = tmpV.Index(idx) // TODO
|
||||
if i+1 == maxI {
|
||||
tmpV.Index(idx).Set(nv)
|
||||
} else {
|
||||
err := setMapByKeys(tmpV.Index(idx), keys[i+1:], nv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rv.SetMapIndex(rftK, tmpV)
|
||||
} else {
|
||||
err = fmt.Errorf("cannot set slice value by named key %s(parent: %s)", nxtKey, key)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf(
|
||||
"map item type is %s(path:%q), cannot set sub-value by path %q",
|
||||
tmpV.Kind(),
|
||||
strings.Join(keys[0:i+1], "."),
|
||||
strings.Join(keys[i+1:], "."),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// deep make map by keys
|
||||
newVal := MakeByKeys(keys[i+1:], nv.Interface())
|
||||
rv.SetMapIndex(rftK, reflect.ValueOf(newVal))
|
||||
}
|
||||
|
||||
break
|
||||
} else if isSlice && strutil.IsInt(key) { // (E). slice from ptr slice
|
||||
idx, _ = strconv.Atoi(key)
|
||||
sliLen := rv.Len()
|
||||
wantLen := idx + 1
|
||||
|
||||
if wantLen > sliLen {
|
||||
elemTyp := rv.Type().Elem()
|
||||
newAdd := reflect.MakeSlice(rv.Type(), 0, wantLen-sliLen)
|
||||
for i := 0; i < wantLen-sliLen; i++ {
|
||||
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
|
||||
}
|
||||
|
||||
rv = reflect.AppendSlice(rv, newAdd)
|
||||
}
|
||||
|
||||
rv = rv.Index(idx)
|
||||
} else {
|
||||
err = fmt.Errorf(
|
||||
"map item type is %s, cannot set sub-value by path %q",
|
||||
rv.Kind(),
|
||||
strings.Join(keys[i:], "."),
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getRealVal(rv reflect.Value) (reflect.Value, bool) {
|
||||
// get real type: any -> map
|
||||
if rv.Kind() == reflect.Interface {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
|
||||
isPtr := false
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
isPtr = true
|
||||
rv = rv.Elem()
|
||||
}
|
||||
|
||||
return rv, isPtr
|
||||
}
|
||||
|
||||
// "arr[2]" => "arr", 2, true
|
||||
func parseArrKeyIndex(key string) (string, int, bool) {
|
||||
pos := strings.IndexRune(key, '[')
|
||||
if pos < 1 || !strings.HasSuffix(key, "]") {
|
||||
return key, 0, false
|
||||
}
|
||||
|
||||
var idx int
|
||||
var err error
|
||||
|
||||
idxStr := key[pos+1 : len(key)-1]
|
||||
if idxStr != "" {
|
||||
idx, err = strconv.Atoi(idxStr)
|
||||
if err != nil {
|
||||
return key, 0, false
|
||||
}
|
||||
}
|
||||
|
||||
key = key[:pos]
|
||||
return key, idx, true
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// SM is alias of map[string]string
|
||||
type SM = StrMap
|
||||
// SMap and StrMap is alias of map[string]string
|
||||
type SMap = StrMap
|
||||
type StrMap map[string]string
|
||||
|
||||
// IsEmpty of the data map
|
||||
func (m StrMap) IsEmpty() bool {
|
||||
return len(m) == 0
|
||||
}
|
||||
|
||||
// Has key on the data map
|
||||
func (m StrMap) Has(key string) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// HasValue on the data map
|
||||
func (m StrMap) HasValue(val string) bool {
|
||||
for _, v := range m {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Load data to the map
|
||||
func (m StrMap) Load(data map[string]string) {
|
||||
for k, v := range data {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Set value to the data map
|
||||
func (m StrMap) Set(key string, val any) {
|
||||
m[key] = strutil.MustString(val)
|
||||
}
|
||||
|
||||
// Value get from the data map
|
||||
func (m StrMap) Value(key string) (string, bool) {
|
||||
val, ok := m[key]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Default get value by key. if not found, return defVal
|
||||
func (m StrMap) Default(key, defVal string) string {
|
||||
if val, ok := m[key]; ok {
|
||||
return val
|
||||
}
|
||||
return defVal
|
||||
}
|
||||
|
||||
// Get value by key
|
||||
func (m StrMap) Get(key string) string {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
// Int value get
|
||||
func (m StrMap) Int(key string) int {
|
||||
if val, ok := m[key]; ok {
|
||||
return mathutil.SafeInt(val)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Int64 value get
|
||||
func (m StrMap) Int64(key string) int64 {
|
||||
if val, ok := m[key]; ok {
|
||||
return mathutil.SafeInt64(val)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Str value get
|
||||
func (m StrMap) Str(key string) string {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
// StrOne get first founded value by keys
|
||||
func (m SMap) StrOne(keys ...string) string {
|
||||
for _, key := range keys {
|
||||
if val, ok := m[key]; ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Bool value get
|
||||
func (m StrMap) Bool(key string) bool {
|
||||
if val, ok := m[key]; ok {
|
||||
return strutil.SafeBool(val)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Ints value to []int
|
||||
func (m StrMap) Ints(key string) []int {
|
||||
if val, ok := m[key]; ok {
|
||||
return strutil.Ints(val, ValSepStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Strings value to []string
|
||||
func (m StrMap) Strings(key string) (ss []string) {
|
||||
if val, ok := m[key]; ok {
|
||||
return strutil.ToSlice(val, ValSepStr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IfExist key, then call the fn with value.
|
||||
func (m StrMap) IfExist(key string, fn func(val string)) {
|
||||
if val, ok := m[key]; ok {
|
||||
fn(val)
|
||||
}
|
||||
}
|
||||
|
||||
// IfValid value is not empty, then call the fn
|
||||
func (m StrMap) IfValid(key string, fn func(val string)) {
|
||||
if val, ok := m[key]; ok && val != "" {
|
||||
fn(val)
|
||||
}
|
||||
}
|
||||
|
||||
// Keys of the string-map
|
||||
func (m StrMap) Keys() []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values of the string-map
|
||||
func (m StrMap) Values() []string {
|
||||
ss := make([]string, 0, len(m))
|
||||
for _, v := range m {
|
||||
ss = append(ss, v)
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
// ToKVPairs slice convert. eg: {k1:v1,k2:v2} => {k1,v1,k2,v2}
|
||||
func (m StrMap) ToKVPairs() []string {
|
||||
pairs := make([]string, 0, len(m)*2)
|
||||
for k, v := range m {
|
||||
pairs = append(pairs, k, v)
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
|
||||
// String data to string
|
||||
func (m StrMap) String() string {
|
||||
return ToString2(m)
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package maputil
|
||||
|
||||
import "github.com/gookit/goutil/strutil"
|
||||
|
||||
// L2StrMap is alias of map[string]map[string]string
|
||||
type L2StrMap map[string]map[string]string
|
||||
|
||||
// Load data, merge new data to old
|
||||
func (m L2StrMap) Load(mp map[string]map[string]string) {
|
||||
for k, v := range mp {
|
||||
if oldV, ok := m[k]; ok {
|
||||
for k1, v1 := range v {
|
||||
oldV[k1] = v1
|
||||
}
|
||||
m[k] = oldV
|
||||
} else {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Value get by key path. eg: "top.sub"
|
||||
func (m L2StrMap) Value(key string) (val string, ok bool) {
|
||||
top, sub, found := strutil.Cut(key, KeySepStr)
|
||||
if !found {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if vals, ok1 := m[top]; ok1 {
|
||||
val, ok = vals[sub]
|
||||
return
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Get value by key path. eg: "top.sub"
|
||||
func (m L2StrMap) Get(key string) string {
|
||||
val, _ := m.Value(key)
|
||||
return val
|
||||
}
|
||||
|
||||
// Exists check key path exists. eg: "top.sub"
|
||||
func (m L2StrMap) Exists(key string) bool {
|
||||
_, ok := m.Value(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// StrMap get by top key. eg: "top"
|
||||
func (m L2StrMap) StrMap(top string) StrMap {
|
||||
return m[top]
|
||||
}
|
||||
Reference in New Issue
Block a user