You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
7.5 KiB
Go
340 lines
7.5 KiB
Go
1 year ago
|
package nmap
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"git.noahlan.cn/noahlan/ntool/nstr"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// 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 nstr.IsNumberStr(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 nstr.IsNumberStr(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 && nstr.IsNumberStr(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
|
||
|
}
|