mirror of
https://github.com/zeromicro/go-zero.git
synced 2025-02-03 00:38:40 +08:00
fix: camel cased key of map item in config (#2715)
* fix: camel cased key of map item in config * fix: mapping anonymous problem * fix: mapping anonymous problem * chore: refactor * chore: add more tests * chore: refactor
This commit is contained in:
parent
f0d1722bbd
commit
affbcb5698
@ -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
|
||||||
|
@ -283,6 +283,10 @@ func TestToCamelCase(t *testing.T) {
|
|||||||
input: "Hello World Foo_Bar",
|
input: "Hello World Foo_Bar",
|
||||||
expect: "hello world fooBar",
|
expect: "hello world fooBar",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "Hello.World Foo_Bar",
|
||||||
|
expect: "hello.world fooBar",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: "你好 World Foo_Bar",
|
input: "你好 World Foo_Bar",
|
||||||
expect: "你好 world fooBar",
|
expect: "你好 world fooBar",
|
||||||
@ -328,6 +332,84 @@ func TestLoadFromYamlBytes(t *testing.T) {
|
|||||||
assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
|
assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadFromYamlBytesLayers(t *testing.T) {
|
||||||
|
input := []byte(`layer1:
|
||||||
|
layer2:
|
||||||
|
layer3: foo`)
|
||||||
|
var val struct {
|
||||||
|
Value string `json:"Layer1.Layer2.Layer3"`
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, LoadFromYamlBytes(input, &val))
|
||||||
|
assert.Equal(t, "foo", val.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesMap(t *testing.T) {
|
||||||
|
input := []byte(`{"foo":{"/mtproto.RPCTos": "bff.bff","bar":"baz"}}`)
|
||||||
|
|
||||||
|
var val struct {
|
||||||
|
Foo map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
||||||
|
assert.Equal(t, "bff.bff", val.Foo["/mtproto.RPCTos"])
|
||||||
|
assert.Equal(t, "baz", val.Foo["bar"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesMapWithSliceElements(t *testing.T) {
|
||||||
|
input := []byte(`{"foo":{"/mtproto.RPCTos": ["bff.bff", "any"],"bar":["baz", "qux"]}}`)
|
||||||
|
|
||||||
|
var val struct {
|
||||||
|
Foo map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
||||||
|
assert.EqualValues(t, []string{"bff.bff", "any"}, val.Foo["/mtproto.RPCTos"])
|
||||||
|
assert.EqualValues(t, []string{"baz", "qux"}, val.Foo["bar"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesMapWithSliceOfStructs(t *testing.T) {
|
||||||
|
input := []byte(`{"foo":{
|
||||||
|
"/mtproto.RPCTos": [{"bar": "any"}],
|
||||||
|
"bar":[{"bar": "qux"}, {"bar": "ever"}]}}`)
|
||||||
|
|
||||||
|
var val struct {
|
||||||
|
Foo map[string][]struct {
|
||||||
|
Bar string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
||||||
|
assert.Equal(t, 1, len(val.Foo["/mtproto.RPCTos"]))
|
||||||
|
assert.Equal(t, "any", val.Foo["/mtproto.RPCTos"][0].Bar)
|
||||||
|
assert.Equal(t, 2, len(val.Foo["bar"]))
|
||||||
|
assert.Equal(t, "qux", val.Foo["bar"][0].Bar)
|
||||||
|
assert.Equal(t, "ever", val.Foo["bar"][1].Bar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesWithAnonymousField(t *testing.T) {
|
||||||
|
type (
|
||||||
|
Int int
|
||||||
|
|
||||||
|
InnerConf struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf struct {
|
||||||
|
Int
|
||||||
|
InnerConf
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
input = []byte(`{"Name": "hello", "int": 3}`)
|
||||||
|
c Conf
|
||||||
|
)
|
||||||
|
assert.NoError(t, LoadFromJsonBytes(input, &c))
|
||||||
|
assert.Equal(t, "hello", c.Name)
|
||||||
|
assert.Equal(t, Int(3), c.Int)
|
||||||
|
}
|
||||||
|
|
||||||
func createTempFile(ext, text string) (string, error) {
|
func createTempFile(ext, text string) (string, error) {
|
||||||
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
|
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -376,19 +376,51 @@ func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value ref
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, hasValue := getValue(m, key); hasValue {
|
|
||||||
return fmt.Errorf("fields of %s can't be wrapped inside, because it's anonymous", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.optional() {
|
if options.optional() {
|
||||||
return u.processAnonymousFieldOptional(field.Type, value, key, m, fullName)
|
return u.processAnonymousFieldOptional(field, value, key, m, fullName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.processAnonymousFieldRequired(field.Type, value, m, fullName)
|
return u.processAnonymousFieldRequired(field, value, m, fullName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processAnonymousFieldOptional(fieldType reflect.Type, value reflect.Value,
|
func (u *Unmarshaler) processAnonymousFieldOptional(field reflect.StructField, value reflect.Value,
|
||||||
key string, m valuerWithParent, fullName string) error {
|
key string, m valuerWithParent, fullName string) error {
|
||||||
|
derefedFieldType := Deref(field.Type)
|
||||||
|
|
||||||
|
switch derefedFieldType.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
return u.processAnonymousStructFieldOptional(field.Type, value, key, m, fullName)
|
||||||
|
default:
|
||||||
|
return u.processNamedField(field, value, m, fullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unmarshaler) processAnonymousFieldRequired(field reflect.StructField, value reflect.Value,
|
||||||
|
m valuerWithParent, fullName string) error {
|
||||||
|
fieldType := field.Type
|
||||||
|
maybeNewValue(fieldType, value)
|
||||||
|
derefedFieldType := Deref(fieldType)
|
||||||
|
indirectValue := reflect.Indirect(value)
|
||||||
|
|
||||||
|
switch derefedFieldType.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < derefedFieldType.NumField(); i++ {
|
||||||
|
if err := u.processField(derefedFieldType.Field(i), indirectValue.Field(i),
|
||||||
|
m, fullName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := u.processNamedField(field, indirectValue, m, fullName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unmarshaler) processAnonymousStructFieldOptional(fieldType reflect.Type,
|
||||||
|
value reflect.Value, key string, m valuerWithParent, fullName string) error {
|
||||||
var filled bool
|
var filled bool
|
||||||
var required int
|
var required int
|
||||||
var requiredFilled int
|
var requiredFilled int
|
||||||
@ -428,21 +460,6 @@ func (u *Unmarshaler) processAnonymousFieldOptional(fieldType reflect.Type, valu
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processAnonymousFieldRequired(fieldType reflect.Type, value reflect.Value,
|
|
||||||
m valuerWithParent, fullName string) error {
|
|
||||||
maybeNewValue(fieldType, value)
|
|
||||||
derefedFieldType := Deref(fieldType)
|
|
||||||
indirectValue := reflect.Indirect(value)
|
|
||||||
|
|
||||||
for i := 0; i < derefedFieldType.NumField(); i++ {
|
|
||||||
if err := u.processField(derefedFieldType.Field(i), indirectValue.Field(i), m, fullName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Value,
|
||||||
m valuerWithParent, fullName string) error {
|
m valuerWithParent, fullName string) error {
|
||||||
if usingDifferentKeys(u.key, field) {
|
if usingDifferentKeys(u.key, field) {
|
||||||
|
@ -212,6 +212,24 @@ func TestUnmarshalIntPtr(t *testing.T) {
|
|||||||
assert.Equal(t, 1, *in.Int)
|
assert.Equal(t, 1, *in.Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalIntSliceOfPtr(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Ints []*int `key:"ints"`
|
||||||
|
}
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"ints": []int{1, 2, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.NoError(t, UnmarshalKey(m, &in))
|
||||||
|
assert.NotEmpty(t, in.Ints)
|
||||||
|
var ints []int
|
||||||
|
for _, i := range in.Ints {
|
||||||
|
ints = append(ints, *i)
|
||||||
|
}
|
||||||
|
assert.EqualValues(t, []int{1, 2, 3}, ints)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalIntWithDefault(t *testing.T) {
|
func TestUnmarshalIntWithDefault(t *testing.T) {
|
||||||
type inner struct {
|
type inner struct {
|
||||||
Int int `key:"int,default=5"`
|
Int int `key:"int,default=5"`
|
||||||
@ -3665,6 +3683,7 @@ func TestUnmarshalJsonBytesSliceOfMaps(t *testing.T) {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ActualAmount int `json:"actual_amount"`
|
ActualAmount int `json:"actual_amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
OrderApplyRefundReq struct {
|
OrderApplyRefundReq struct {
|
||||||
OrderId string `json:"order_id"`
|
OrderId string `json:"order_id"`
|
||||||
RefundReason RefundReasonData `json:"refund_reason,optional"`
|
RefundReason RefundReasonData `json:"refund_reason,optional"`
|
||||||
@ -3676,6 +3695,130 @@ func TestUnmarshalJsonBytesSliceOfMaps(t *testing.T) {
|
|||||||
assert.NoError(t, UnmarshalJsonBytes(input, &req))
|
assert.NoError(t, UnmarshalJsonBytes(input, &req))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesWithAnonymousField(t *testing.T) {
|
||||||
|
type (
|
||||||
|
Int int
|
||||||
|
|
||||||
|
InnerConf struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf struct {
|
||||||
|
Int
|
||||||
|
InnerConf
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
input = []byte(`{"Name": "hello", "Int": 3}`)
|
||||||
|
c Conf
|
||||||
|
)
|
||||||
|
assert.NoError(t, UnmarshalJsonBytes(input, &c))
|
||||||
|
assert.Equal(t, "hello", c.Name)
|
||||||
|
assert.Equal(t, Int(3), c.Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesWithAnonymousFieldOptional(t *testing.T) {
|
||||||
|
type (
|
||||||
|
Int int
|
||||||
|
|
||||||
|
InnerConf struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf struct {
|
||||||
|
Int `json:",optional"`
|
||||||
|
InnerConf
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
input = []byte(`{"Name": "hello", "Int": 3}`)
|
||||||
|
c Conf
|
||||||
|
)
|
||||||
|
assert.NoError(t, UnmarshalJsonBytes(input, &c))
|
||||||
|
assert.Equal(t, "hello", c.Name)
|
||||||
|
assert.Equal(t, Int(3), c.Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesWithAnonymousFieldBadTag(t *testing.T) {
|
||||||
|
type (
|
||||||
|
Int int
|
||||||
|
|
||||||
|
InnerConf struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf struct {
|
||||||
|
Int `json:",optional=123"`
|
||||||
|
InnerConf
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
input = []byte(`{"Name": "hello", "Int": 3}`)
|
||||||
|
c Conf
|
||||||
|
)
|
||||||
|
assert.Error(t, UnmarshalJsonBytes(input, &c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesWithAnonymousFieldBadValue(t *testing.T) {
|
||||||
|
type (
|
||||||
|
Int int
|
||||||
|
|
||||||
|
InnerConf struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf struct {
|
||||||
|
Int
|
||||||
|
InnerConf
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
input = []byte(`{"Name": "hello", "Int": "3"}`)
|
||||||
|
c Conf
|
||||||
|
)
|
||||||
|
assert.Error(t, UnmarshalJsonBytes(input, &c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesWithAnonymousFieldBadTagInStruct(t *testing.T) {
|
||||||
|
type (
|
||||||
|
InnerConf struct {
|
||||||
|
Name string `json:",optional=123"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf struct {
|
||||||
|
InnerConf `json:",optional"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
input = []byte(`{"Name": "hello"}`)
|
||||||
|
c Conf
|
||||||
|
)
|
||||||
|
assert.Error(t, UnmarshalJsonBytes(input, &c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJsonBytesWithAnonymousFieldNotInOptions(t *testing.T) {
|
||||||
|
type (
|
||||||
|
InnerConf struct {
|
||||||
|
Name string `json:",options=[a,b]"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf struct {
|
||||||
|
InnerConf `json:",optional"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
input = []byte(`{"Name": "hello"}`)
|
||||||
|
c Conf
|
||||||
|
)
|
||||||
|
assert.Error(t, UnmarshalJsonBytes(input, &c))
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkDefaultValue(b *testing.B) {
|
func BenchmarkDefaultValue(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var a struct {
|
var a struct {
|
||||||
|
@ -31,3 +31,27 @@ func TestMapValuerWithInherit_Value(t *testing.T) {
|
|||||||
assert.Equal(t, "localhost", m["host"])
|
assert.Equal(t, "localhost", m["host"])
|
||||||
assert.Equal(t, 8080, m["port"])
|
assert.Equal(t, 8080, m["port"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRecursiveValuer_Value(t *testing.T) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"foo": "value",
|
||||||
|
}
|
||||||
|
valuer := recursiveValuer{
|
||||||
|
current: mapValuer(input["component"].(map[string]interface{})),
|
||||||
|
parent: simpleValuer{
|
||||||
|
current: mapValuer(input),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := valuer.Value("foo")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.EqualValues(t, map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
}, val)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user