switch to go vendoring
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/...
|
||||
```
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package maputil
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Aliases implemented a simple string alias map.
|
||||
type Aliases map[string]string
|
||||
|
||||
// AddAlias to the Aliases
|
||||
func (as Aliases) AddAlias(real, alias 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
|
||||
func (as Aliases) AddAliases(real string, aliases []string) {
|
||||
for _, a := range aliases {
|
||||
as.AddAlias(real, a)
|
||||
}
|
||||
}
|
||||
|
||||
// AddAliasMap to the Aliases
|
||||
func (as Aliases) AddAliasMap(alias2real map[string]string) {
|
||||
for a, r := range alias2real {
|
||||
as.AddAlias(r, a)
|
||||
}
|
||||
}
|
||||
|
||||
// HasAlias in the Aliases
|
||||
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
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
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
|
||||
}
|
||||
|
||||
// HasAllKeys check of the given map.
|
||||
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
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/reflects"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// KeyToLower convert keys to lower case.
|
||||
func KeyToLower(src map[string]string) map[string]string {
|
||||
newMp := make(map[string]string, len(src))
|
||||
for k, v := range src {
|
||||
k = strings.ToLower(k)
|
||||
newMp[k] = v
|
||||
}
|
||||
|
||||
return newMp
|
||||
}
|
||||
|
||||
// ToStringMap convert map[string]any to map[string]string
|
||||
func ToStringMap(src map[string]any) map[string]string {
|
||||
newMp := make(map[string]string, len(src))
|
||||
for k, v := range src {
|
||||
newMp[k] = strutil.MustString(v)
|
||||
}
|
||||
|
||||
return newMp
|
||||
}
|
||||
|
||||
// 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, "&")
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* 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)
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// Data an map data type
|
||||
type Data map[string]any
|
||||
type Map = Data
|
||||
|
||||
// Has value on the data map
|
||||
func (d Data) Has(key string) bool {
|
||||
_, ok := d.GetByPath(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsEmtpy if the data map
|
||||
func (d Data) IsEmtpy() bool {
|
||||
return len(d) == 0
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 key path.
|
||||
if strings.ContainsRune(path, '.') {
|
||||
val, ok := GetByPath(path, d)
|
||||
if ok {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// For 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.
|
||||
//
|
||||
// For 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (d Data) Int(key string) int {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return mathutil.QuietInt(val)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Int64 value get
|
||||
func (d Data) Int64(key string) int64 {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return mathutil.QuietInt64(val)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Str value get by key
|
||||
func (d Data) Str(key string) string {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return strutil.QuietString(val)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Bool value get
|
||||
func (d Data) Bool(key string) bool {
|
||||
val, ok := d.GetByPath(key)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if bl, ok := val.(bool); ok {
|
||||
return bl
|
||||
}
|
||||
|
||||
if str, ok := val.(string); ok {
|
||||
return strutil.QuietBool(str)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Strings get []string value
|
||||
func (d Data) Strings(key string) []string {
|
||||
val, ok := d.GetByPath(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ss, ok := val.([]string); ok {
|
||||
return ss
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StrSplit get strings by split key value
|
||||
func (d Data) StrSplit(key, sep string) []string {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return strings.Split(strutil.QuietString(val), sep)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringsByStr value get by key
|
||||
func (d Data) StringsByStr(key string) []string {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
return strings.Split(strutil.QuietString(val), ",")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringMap get map[string]string value
|
||||
func (d Data) StringMap(key string) map[string]string {
|
||||
val, ok := d.GetByPath(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if smp, ok := val.(map[string]string); ok {
|
||||
return smp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sub get sub value as new Data
|
||||
func (d Data) Sub(key string) Data {
|
||||
if val, ok := d.GetByPath(key); ok {
|
||||
if sub, ok := val.(map[string]any); ok {
|
||||
return sub
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
+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() {
|
||||
kStr := strutil.QuietString(key.Interface())
|
||||
if indentLn > 0 {
|
||||
buf.WriteString(f.Indent)
|
||||
}
|
||||
|
||||
buf.WriteString(kStr)
|
||||
buf.WriteByte(':')
|
||||
|
||||
vStr := strutil.QuietString(rv.MapIndex(key).Interface())
|
||||
buf.WriteString(vStr)
|
||||
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('}')
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 val, ok := mp[path]; ok {
|
||||
return val, true
|
||||
}
|
||||
|
||||
// no sub key
|
||||
if len(mp) == 0 || !strings.ContainsRune(path, '.') {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// has sub key. eg. "top.sub"
|
||||
keys := strings.Split(path, ".")
|
||||
topK := keys[0]
|
||||
|
||||
// find top item data use top key
|
||||
var item any
|
||||
if item, ok = mp[topK]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, k := range keys[1:] {
|
||||
switch tData := item.(type) {
|
||||
case map[string]string: // is simple map
|
||||
if item, ok = tData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[string]any: // is map(decode from toml/json)
|
||||
if item, ok = tData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[any]any: // is map(decode from yaml)
|
||||
if item, ok = tData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case []any: // is a slice
|
||||
if item, ok = getBySlice(k, tData); !ok {
|
||||
return
|
||||
}
|
||||
case []string, []int, []float32, []float64, []bool, []rune:
|
||||
slice := reflect.ValueOf(tData)
|
||||
sData := make([]any, slice.Len())
|
||||
for i := 0; i < slice.Len(); i++ {
|
||||
sData[i] = slice.Index(i).Interface()
|
||||
}
|
||||
if item, ok = getBySlice(k, sData); !ok {
|
||||
return
|
||||
}
|
||||
default: // error
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
return item, true
|
||||
}
|
||||
|
||||
func getBySlice(k string, slice []any) (val any, ok bool) {
|
||||
i, err := strconv.ParseInt(k, 10, 64)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if size := int64(len(slice)); i >= size {
|
||||
return nil, false
|
||||
}
|
||||
return slice[i], 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
|
||||
}
|
||||
|
||||
// Values get all values from the given map.
|
||||
func Values(mp any) (values []any) {
|
||||
rftVal := reflect.Indirect(reflect.ValueOf(mp))
|
||||
if rftVal.Kind() != reflect.Map {
|
||||
return
|
||||
}
|
||||
|
||||
values = make([]any, 0, rftVal.Len())
|
||||
for _, key := range rftVal.MapKeys() {
|
||||
values = append(values, rftVal.MapIndex(key).Interface())
|
||||
}
|
||||
return
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// 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 = '.'
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// MergeStringMap simple merge two string map. merge src to dst map
|
||||
func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string {
|
||||
for k, v := range src {
|
||||
if ignoreCase {
|
||||
k = strings.ToLower(k)
|
||||
}
|
||||
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// 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.IsNumeric(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.IsNumeric(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.IsNumeric(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
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// SMap is alias of map[string]string
|
||||
type SMap map[string]string
|
||||
|
||||
// IsEmpty of the data map
|
||||
func (m SMap) IsEmpty() bool {
|
||||
return len(m) == 0
|
||||
}
|
||||
|
||||
// Has key on the data map
|
||||
func (m SMap) Has(key string) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// HasValue on the data map
|
||||
func (m SMap) HasValue(val string) bool {
|
||||
for _, v := range m {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Value get from the data map
|
||||
func (m SMap) Value(key string) (string, bool) {
|
||||
val, ok := m[key]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Default get value by key. if not found, return defVal
|
||||
func (m SMap) Default(key, defVal string) string {
|
||||
if val, ok := m[key]; ok {
|
||||
return val
|
||||
}
|
||||
return defVal
|
||||
}
|
||||
|
||||
// Get value by key
|
||||
func (m SMap) Get(key string) string {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
// Int value get
|
||||
func (m SMap) Int(key string) int {
|
||||
if val, ok := m[key]; ok {
|
||||
return mathutil.QuietInt(val)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Int64 value get
|
||||
func (m SMap) Int64(key string) int64 {
|
||||
if val, ok := m[key]; ok {
|
||||
return mathutil.QuietInt64(val)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Str value get
|
||||
func (m SMap) Str(key string) string {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
// Bool value get
|
||||
func (m SMap) Bool(key string) bool {
|
||||
if val, ok := m[key]; ok {
|
||||
return strutil.QuietBool(val)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Ints value to []int
|
||||
func (m SMap) Ints(key string) []int {
|
||||
if val, ok := m[key]; ok {
|
||||
return strutil.Ints(val, ValSepStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Strings value to []string
|
||||
func (m SMap) Strings(key string) (ss []string) {
|
||||
if val, ok := m[key]; ok {
|
||||
return strutil.ToSlice(val, ValSepStr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Keys of the string-map
|
||||
func (m SMap) 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 SMap) Values() []string {
|
||||
ss := make([]string, 0, len(m))
|
||||
for _, v := range m {
|
||||
ss = append(ss, v)
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
// String data to string
|
||||
func (m SMap) String() string {
|
||||
return ToString2(m)
|
||||
}
|
||||
Reference in New Issue
Block a user