Skip to content

Commit

Permalink
merge same tag in array, solve v2fly/discussion#97
Browse files Browse the repository at this point in the history
  • Loading branch information
qjebbs committed Nov 27, 2020
1 parent 05846c4 commit 2c4f5dc
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 111 deletions.
18 changes: 12 additions & 6 deletions commands/all/merge_doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ The 1st one:
{
"log": {"access": "some_value", "loglevel": "debug"},
"inbounds": [{"tag": "in-1"}],
"outbounds": [{"priority": 100, "tag": "out-1"}],
"routing": {"rules": [{"inboundTag":["in-1"],"outboundTag":"out-1"}]}
"outbounds": [{"_priority": 100, "tag": "out-1"}],
"routing": {"rules": [
{"_tag":"default_route","inboundTag":["in-1"],"outboundTag":"out-1"}
]}
}
The 2nd one:
{
"log": {"loglevel": "error"},
"inbounds": [{"tag": "in-2"}],
"outbounds": [{"priority": -100, "tag": "out-2"}],
"routing": {"rules": [{"inboundTag":["in-2"],"outboundTag":"out-2"}]}
"outbounds": [{"_priority": -100, "tag": "out-2"}],
"routing": {"rules": [
{"inboundTag":["in-2"],"outboundTag":"out-2"},
{"_tag":"default_route","inboundTag":["in-1.1"]}
]}
}
Output:
Expand All @@ -44,14 +49,15 @@ Output:
{"tag": "out-1"}
],
"routing": {"rules": [
{"inboundTag":["in-1"],"outboundTag":"out-1"}
{"inboundTag":["in-1","in-1.1"],"outboundTag":"out-1"}
{"inboundTag":["in-2"],"outboundTag":"out-2"}
]}
}
Explained:
- Simple values (string, number, boolean) are override, all others are merged
- Add "priority" property to array elements will help sort the array
- Add "_priority" property to array elements will help sort the array
- Items with same "tag" (or "_tag") in an array will be merged
`,
}
119 changes: 119 additions & 0 deletions infra/conf/merge/extra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package merge

import "sort"

func getPriority(v interface{}) float64 {
var m map[string]interface{}
var ok bool
if m, ok = v.(map[string]interface{}); !ok {
return 0
}
if i, ok := m["_priority"]; ok {
if p, ok := i.(float64); ok {
return p
}
}
return 0
}

func getTag(v map[string]interface{}) string {
if field, ok := v["tag"]; ok {
if t, ok := field.(string); ok {
return t
}
}
if field, ok := v["_tag"]; ok {
if t, ok := field.(string); ok {
return t
}
}
return ""
}

func mergeSliceItems(s []interface{}) ([]interface{}, error) {
// from: [a,"",b,"",a,"",b,""]
// to: [a,"",b,"",nil,"",nil,""]
for i, item1 := range s {
// if slice, ok := item.([]interface{}); ok {
// mergeSameTagInSlice(slice)
// continue
// }
map1, ok := item1.(map[string]interface{})
if !ok {
continue
}
tag1 := getTag(map1)
if tag1 == "" {
continue
}
for j := i + 1; j < len(s); j++ {
map2, ok := s[j].(map[string]interface{})
if !ok {
continue
}
tag2 := getTag(map2)
if tag1 == tag2 {
s[j] = nil
m, err := Maps(map1, map2)
if err != nil {
return nil, err
}
s[i] = m
}
}
}
ns := make([]interface{}, 0)
for _, item := range s {
if item == nil {
continue
}
ns = append(ns, item)
}
return ns, nil
}

// sortSlicesInMap sort slices in map by field "priority"
func sortSlicesInMap(target map[string]interface{}) {
for key, value := range target {
if slice, ok := value.([]interface{}); ok {
sort.Slice(slice, func(i, j int) bool { return getPriority(slice[i]) < getPriority(slice[j]) })
target[key] = slice
} else if field, ok := value.(map[string]interface{}); ok {
sortSlicesInMap(field)
}
}
}
func removeHelperKey(target map[string]interface{}) {
for key, value := range target {
if key == "_priority" || key == "_tag" {
delete(target, key)
} else if slice, ok := value.([]interface{}); ok {
for _, e := range slice {
if el, ok := e.(map[string]interface{}); ok {
removeHelperKey(el)
}
}
} else if field, ok := value.(map[string]interface{}); ok {
removeHelperKey(field)
}
}
}
func mergeSliceSameTag(target map[string]interface{}) error {
for key, value := range target {
if slice, ok := value.([]interface{}); ok {
s, err := mergeSliceItems(slice)
if err != nil {
return err
}
target[key] = s
for _, item := range s {
if m, ok := item.(map[string]interface{}); ok {
mergeSliceSameTag(m)
}
}
} else if field, ok := value.(map[string]interface{}); ok {
mergeSliceSameTag(field)
}
}
return nil
}
91 changes: 29 additions & 62 deletions infra/conf/merge/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,80 +4,43 @@ import (
"fmt"
"io"
"reflect"
"sort"

"v2ray.com/core/infra/conf/serial"
)

func isZero(v interface{}) bool {
return getValue(reflect.ValueOf(v)).IsZero()
}

func getPriority(v interface{}) float64 {
var m map[string]interface{}
var ok bool
if m, ok = v.(map[string]interface{}); !ok {
return 0
}
if i, ok := m["priority"]; ok {
if p, ok := i.(float64); ok {
return p
// Maps merges source map into target, and return it
func Maps(target map[string]interface{}, source map[string]interface{}) (out map[string]interface{}, err error) {
for key, value := range source {
// fmt.Printf("[%s] type: %s, kind: %s\n", key, getType(fieldTypeSrc.Type).Name(), getType(fieldTypeSrc.Type).Kind())
target[key], err = mergeField(target[key], value)
if err != nil {
return nil, err
}
}
return 0
return target, nil
}
func sortSlicesInMap(target map[string]interface{}) {
for key, value := range target {
if slice, ok := value.([]interface{}); ok {
sort.Slice(slice, func(i, j int) bool { return getPriority(slice[i]) < getPriority(slice[j]) })
target[key] = slice
} else if field, ok := value.(map[string]interface{}); ok {
sortSlicesInMap(field)
}

func mergeField(target interface{}, source interface{}) (interface{}, error) {
if (source == nil) || isZero(source) {
return target, nil
}
}
func removePriorityKey(target map[string]interface{}) {
for key, value := range target {
if _, ok := value.(float64); key == "priority" && ok {
delete(target, key)
} else if slice, ok := value.([]interface{}); ok {
for _, e := range slice {
if el, ok := e.(map[string]interface{}); ok {
removePriorityKey(el)
}
}
} else if field, ok := value.(map[string]interface{}); ok {
removePriorityKey(field)
}
if target == nil || isZero(target) {
return source, nil
}
}
func mergeMaps(target map[string]interface{}, source map[string]interface{}) error {
for key, value := range source {
// fmt.Printf("[%s] type: %s, kind: %s\n", key, getType(fieldTypeSrc.Type).Name(), getType(fieldTypeSrc.Type).Kind())
if (value == nil) || isZero(value) {
continue
}
if target[key] == nil || isZero(value) {
target[key] = value
continue
if slice, ok := source.([]interface{}); ok {
if tslice, ok := target.([]interface{}); ok {
target = append(tslice, slice...)
return target, nil
}
if slice, ok := value.([]interface{}); ok {
if tslice, ok := target[key].([]interface{}); ok {
target[key] = append(tslice, slice...)
} else {
return fmt.Errorf("value type of key (%s) mismatch, source is 'slice' but target not", key)
}
} else if field, ok := value.(map[string]interface{}); ok {
if mapField, ok := target[key].(map[string]interface{}); ok {
if err := mergeMaps(mapField, field); err != nil {
return err
}
} else {
return fmt.Errorf("value type of key (%s) mismatch, source is 'map[string]interface{}' but target not", key)
}
return nil, fmt.Errorf("value type mismatch, source is 'slice' but target not: %s", source)
} else if smap, ok := source.(map[string]interface{}); ok {
if tmap, ok := target.(map[string]interface{}); ok {
_, err := Maps(tmap, smap)
return tmap, err
}
return nil, fmt.Errorf("value type mismatch, source is 'map[string]interface{}' but target not: %s", source)
}
return nil
return source, nil
}

func jsonToMap(r io.Reader) (map[string]interface{}, error) {
Expand All @@ -89,6 +52,10 @@ func jsonToMap(r io.Reader) (map[string]interface{}, error) {
return c, nil
}

func isZero(v interface{}) bool {
return getValue(reflect.ValueOf(v)).IsZero()
}

func getValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Ptr {
return v.Elem()
Expand Down
10 changes: 7 additions & 3 deletions infra/conf/merge/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func JSONsToMap(args interface{}) (map[string]interface{}, error) {
if err != nil {
return nil, err
}
if err = mergeMaps(conf, m); err != nil {
if _, err = Maps(conf, m); err != nil {
return nil, err
}
}
Expand All @@ -51,15 +51,19 @@ func JSONsToMap(args interface{}) (map[string]interface{}, error) {
if err != nil {
return nil, err
}
if err = mergeMaps(conf, m); err != nil {
if _, err = Maps(conf, m); err != nil {
return nil, err
}
}
default:
return nil, errors.New("unsupport args for JSONsToMap")
}
sortSlicesInMap(conf)
removePriorityKey(conf)
err := mergeSliceSameTag(conf)
if err != nil {
return nil, err
}
removeHelperKey(conf)
return conf, nil
}

Expand Down
Loading

0 comments on commit 2c4f5dc

Please sign in to comment.