Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+72
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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]
}