Add more intricate TypeByDefaultValue logic

In addition to testing the old code, we're now supporting more generic types.  You could set the default value
to a struct type, and with SetTypeByDefualtValue(true), the struct type will be filled in as expected, so long
as the struct type has matching fields in the data.
This commit is contained in:
Samuel Nelson 2016-11-06 00:17:30 -06:00
parent 651d9d916a
commit f3638581fd
2 changed files with 107 additions and 2 deletions

View file

@ -592,7 +592,6 @@ func (v *Viper) Get(key string) interface{} {
valType := val
if v.typeByDefValue {
// TODO(bep) this branch isn't covered by a single test.
path := strings.Split(lcaseKey, v.keyDelim)
defVal := v.searchMap(v.defaults, path)
if defVal != nil {
@ -615,8 +614,53 @@ func (v *Viper) Get(key string) interface{} {
return cast.ToDuration(val)
case []string:
return cast.ToStringSlice(val)
default:
return convert(reflect.ValueOf(val), reflect.TypeOf(valType)).Interface()
}
}
func convert(v reflect.Value, typ reflect.Type) reflect.Value {
if v.Kind() == reflect.Interface {
return convert(v.Elem(), typ)
}
switch typ.Kind() {
case reflect.Ptr:
res := reflect.New(typ.Elem())
res.Elem().Set(convert(v, typ.Elem()))
return res
case reflect.Struct:
if v.Kind() != reflect.Map {
return v
}
res := reflect.New(typ).Elem()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldVal := v.MapIndex(reflect.ValueOf(field.Name))
if !fieldVal.IsValid() {
fieldVal = v.MapIndex(reflect.ValueOf(strings.ToLower(field.Name)))
if !fieldVal.IsValid() {
continue
}
}
res.Field(i).Set(convert(fieldVal, field.Type))
}
return res
case reflect.Slice, reflect.Array:
if v.Kind() != reflect.Slice {
return v
}
res := reflect.Zero(typ)
elemType := typ.Elem()
for i := 0; i < v.Len(); i++ {
res = reflect.Append(res, convert(v.Index(i), elemType))
}
return res
default:
if !v.Type().ConvertibleTo(typ) {
return v
}
return v.Convert(typ)
}
return val
}
// Sub returns new Viper instance representing a sub tree of this instance.

View file

@ -789,6 +789,67 @@ func TestSub(t *testing.T) {
assert.Equal(t, (*Viper)(nil), subv)
}
var yamlTypeExample = []byte(`
hobbies:
- skateboarding
- snowboarding
languages: go golang python
job:
salary: 100,000 USD
medical: full
clothes:
- jacket: leather
trousers: denim
pants:
size: large
- jacket: denim
trousers: slacks
pants:
size: medium
`)
type Clothing struct {
Jacket string
Trousers string
Pants Pants
}
type Pants struct {
Size string
}
type Job struct {
Salary string
Medical string
}
func TestTypeByDefaultValue(t *testing.T) {
v := New()
v.SetConfigType("yaml")
v.SetDefault("hobbies", []string{})
v.SetDefault("languages", []string{})
v.SetDefault("job", &Job{})
v.SetDefault("clothes", []Clothing{})
v.SetTypeByDefaultValue(true)
err := v.ReadConfig(bytes.NewBuffer(yamlTypeExample))
assert.NoError(t, err)
assert.Equal(t, []string{"skateboarding", "snowboarding"}, v.Get("hobbies"))
assert.Equal(t, []string{"go", "golang", "python"}, v.Get("languages"))
assert.Equal(t, []Clothing{
{
Jacket: "leather",
Trousers: "denim",
Pants: Pants{Size: "large"},
},
{
Jacket: "denim",
Trousers: "slacks",
Pants: Pants{Size: "medium"},
},
}, v.Get("clothes"))
}
var yamlMergeExampleTgt = []byte(`
hello:
pop: 37890