|
|
@ -5,6 +5,7 @@ import (
|
|
|
|
"log"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path"
|
|
|
|
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/jsonx"
|
|
|
|
"github.com/zeromicro/go-zero/core/jsonx"
|
|
|
@ -21,6 +22,12 @@ var loaders = map[string]func([]byte, interface{}) error{
|
|
|
|
".yml": LoadFromYamlBytes,
|
|
|
|
".yml": LoadFromYamlBytes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type fieldInfo struct {
|
|
|
|
|
|
|
|
name string
|
|
|
|
|
|
|
|
kind reflect.Kind
|
|
|
|
|
|
|
|
children map[string]fieldInfo
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
|
|
|
|
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
|
|
|
|
func Load(file string, v interface{}, opts ...Option) error {
|
|
|
|
func Load(file string, v interface{}, opts ...Option) error {
|
|
|
|
content, err := os.ReadFile(file)
|
|
|
|
content, err := os.ReadFile(file)
|
|
|
@ -58,7 +65,10 @@ func LoadFromJsonBytes(content []byte, v interface{}) error {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return mapping.UnmarshalJsonMap(toCamelCaseKeyMap(m), v, mapping.WithCanonicalKeyFunc(toCamelCase))
|
|
|
|
finfo := buildFieldsInfo(reflect.TypeOf(v))
|
|
|
|
|
|
|
|
camelCaseKeyMap := toCamelCaseKeyMap(m, finfo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return mapping.UnmarshalJsonMap(camelCaseKeyMap, v, mapping.WithCanonicalKeyFunc(toCamelCase))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// LoadConfigFromJsonBytes loads config into v from content json bytes.
|
|
|
|
// LoadConfigFromJsonBytes loads config into v from content json bytes.
|
|
|
@ -100,6 +110,64 @@ func MustLoad(path string, v interface{}, opts ...Option) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func buildFieldsInfo(tp reflect.Type) map[string]fieldInfo {
|
|
|
|
|
|
|
|
tp = mapping.Deref(tp)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch tp.Kind() {
|
|
|
|
|
|
|
|
case reflect.Struct:
|
|
|
|
|
|
|
|
return buildStructFieldsInfo(tp)
|
|
|
|
|
|
|
|
case reflect.Array, reflect.Slice:
|
|
|
|
|
|
|
|
return buildFieldsInfo(mapping.Deref(tp.Elem()))
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func buildStructFieldsInfo(tp reflect.Type) map[string]fieldInfo {
|
|
|
|
|
|
|
|
info := make(map[string]fieldInfo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for i := 0; i < tp.NumField(); i++ {
|
|
|
|
|
|
|
|
field := tp.Field(i)
|
|
|
|
|
|
|
|
name := field.Name
|
|
|
|
|
|
|
|
ccName := toCamelCase(name)
|
|
|
|
|
|
|
|
ft := mapping.Deref(field.Type)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// flatten anonymous fields
|
|
|
|
|
|
|
|
if field.Anonymous {
|
|
|
|
|
|
|
|
if ft.Kind() == reflect.Struct {
|
|
|
|
|
|
|
|
fields := buildFieldsInfo(ft)
|
|
|
|
|
|
|
|
for k, v := range fields {
|
|
|
|
|
|
|
|
info[k] = v
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
info[ccName] = fieldInfo{
|
|
|
|
|
|
|
|
name: name,
|
|
|
|
|
|
|
|
kind: ft.Kind(),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var fields map[string]fieldInfo
|
|
|
|
|
|
|
|
switch ft.Kind() {
|
|
|
|
|
|
|
|
case reflect.Struct:
|
|
|
|
|
|
|
|
fields = buildFieldsInfo(ft)
|
|
|
|
|
|
|
|
case reflect.Array, reflect.Slice:
|
|
|
|
|
|
|
|
fields = buildFieldsInfo(ft.Elem())
|
|
|
|
|
|
|
|
case reflect.Map:
|
|
|
|
|
|
|
|
fields = buildFieldsInfo(ft.Elem())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
info[ccName] = fieldInfo{
|
|
|
|
|
|
|
|
name: name,
|
|
|
|
|
|
|
|
kind: ft.Kind(),
|
|
|
|
|
|
|
|
children: fields,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return info
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func toCamelCase(s string) string {
|
|
|
|
func toCamelCase(s string) string {
|
|
|
|
var buf strings.Builder
|
|
|
|
var buf strings.Builder
|
|
|
|
buf.Grow(len(s))
|
|
|
|
buf.Grow(len(s))
|
|
|
@ -123,14 +191,19 @@ func toCamelCase(s string) string {
|
|
|
|
if isCap || isLow {
|
|
|
|
if isCap || isLow {
|
|
|
|
buf.WriteRune(v)
|
|
|
|
buf.WriteRune(v)
|
|
|
|
capNext = false
|
|
|
|
capNext = false
|
|
|
|
} else if v == ' ' || v == '\t' {
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch v {
|
|
|
|
|
|
|
|
// '.' is used for chained keys, e.g. "grand.parent.child"
|
|
|
|
|
|
|
|
case ' ', '.', '\t':
|
|
|
|
buf.WriteRune(v)
|
|
|
|
buf.WriteRune(v)
|
|
|
|
capNext = false
|
|
|
|
capNext = false
|
|
|
|
boundary = true
|
|
|
|
boundary = true
|
|
|
|
} else if v == '_' {
|
|
|
|
case '_':
|
|
|
|
capNext = true
|
|
|
|
capNext = true
|
|
|
|
boundary = true
|
|
|
|
boundary = true
|
|
|
|
} else {
|
|
|
|
default:
|
|
|
|
buf.WriteRune(v)
|
|
|
|
buf.WriteRune(v)
|
|
|
|
capNext = true
|
|
|
|
capNext = true
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -139,14 +212,14 @@ func toCamelCase(s string) string {
|
|
|
|
return buf.String()
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func toCamelCaseInterface(v interface{}) interface{} {
|
|
|
|
func toCamelCaseInterface(v interface{}, info map[string]fieldInfo) interface{} {
|
|
|
|
switch vv := v.(type) {
|
|
|
|
switch vv := v.(type) {
|
|
|
|
case map[string]interface{}:
|
|
|
|
case map[string]interface{}:
|
|
|
|
return toCamelCaseKeyMap(vv)
|
|
|
|
return toCamelCaseKeyMap(vv, info)
|
|
|
|
case []interface{}:
|
|
|
|
case []interface{}:
|
|
|
|
var arr []interface{}
|
|
|
|
var arr []interface{}
|
|
|
|
for _, vvv := range vv {
|
|
|
|
for _, vvv := range vv {
|
|
|
|
arr = append(arr, toCamelCaseInterface(vvv))
|
|
|
|
arr = append(arr, toCamelCaseInterface(vvv, info))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arr
|
|
|
|
return arr
|
|
|
|
default:
|
|
|
|
default:
|
|
|
@ -154,10 +227,22 @@ func toCamelCaseInterface(v interface{}) interface{} {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func toCamelCaseKeyMap(m map[string]interface{}) map[string]interface{} {
|
|
|
|
func toCamelCaseKeyMap(m map[string]interface{}, info map[string]fieldInfo) map[string]interface{} {
|
|
|
|
res := make(map[string]interface{})
|
|
|
|
res := make(map[string]interface{})
|
|
|
|
|
|
|
|
|
|
|
|
for k, v := range m {
|
|
|
|
for k, v := range m {
|
|
|
|
res[toCamelCase(k)] = toCamelCaseInterface(v)
|
|
|
|
ti, ok := info[k]
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
res[k] = toCamelCaseInterface(v, ti.children)
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cck := toCamelCase(k)
|
|
|
|
|
|
|
|
if ti, ok = info[cck]; ok {
|
|
|
|
|
|
|
|
res[toCamelCase(k)] = toCamelCaseInterface(v, ti.children)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
res[k] = v
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return res
|
|
|
|
return res
|
|
|
|