From 68a8b52d7b917bf06730c9f1fa6d972e3e8f2fe7 Mon Sep 17 00:00:00 2001 From: "laxman.vallandas" Date: Thu, 15 Jun 2017 13:50:46 +0000 Subject: [PATCH] #358: access nested array values using get --- viper.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/viper.go b/viper.go index 31b41a6..f80570b 100644 --- a/viper.go +++ b/viper.go @@ -28,6 +28,7 @@ import ( "os" "path/filepath" "reflect" + "strconv" "strings" "time" @@ -53,7 +54,7 @@ func init() { type remoteConfigFactory interface { Get(rp RemoteProvider) (io.Reader, error) Watch(rp RemoteProvider) (io.Reader, error) - WatchChannel(rp RemoteProvider)(<-chan *RemoteResponse, chan bool) + WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool) } // RemoteConfig is optional, see the remote package @@ -492,6 +493,56 @@ func (v *Viper) searchMapWithPathPrefixes(source map[string]interface{}, path [] return nil } +func (v *Viper) searchMapWithArrayPrefix(source map[string]interface{}, path []string) interface{} { + if len(path) == 0 { + return source + } + + next, ok := source[path[0]] + if ok { + // Immediate Key Value + if len(path) == 1 { + return next + } + + // Value from Nested key + switch next.(type) { + case map[interface{}]interface{}: + return v.searchMapWithArrayPrefix(cast.ToStringMap(next), path[1:]) + case map[string]interface{}: + // Type assertion is safe here since it is only reached + // if the type of `next` is the same as the type being asserted + return v.searchMapWithArrayPrefix(next.(map[string]interface{}), path[1:]) + case []interface{}: + v1 := cast.ToSlice(next) + for k2, v2 := range v1 { + if reflect.TypeOf(v2).Kind() == reflect.Map { + if _, err := strconv.ParseInt(path[1], 10, 64); err == nil { + if strconv.Itoa(k2) == path[1] { + return v.searchMapWithArrayPrefix(cast.ToStringMap(v2), path[1:]) + } + } else { + return v.searchMapWithArrayPrefix(cast.ToStringMap(v2), path[1:]) + } + } else { + if _, err := strconv.ParseInt(path[1], 10, 64); err == nil { + if strconv.Itoa(k2) == path[1] { + return v2 + } + } + } + } + default: + return nil + } + } else { + if _, err := strconv.ParseInt(path[0], 10, 64); err == nil { + return v.searchMapWithArrayPrefix(source, path[1:]) + } + } + return nil +} + // isPathShadowedInDeepMap makes sure the given path is not shadowed somewhere // on its path in the map. // e.g., if "foo.bar" has a value in the given map, it “shadows” @@ -858,7 +909,6 @@ func (v *Viper) BindEnv(input ...string) error { // Viper will check to see if an alias exists first. // Note: this assumes a lower-cased key given. func (v *Viper) find(lcaseKey string) interface{} { - var ( val interface{} exists bool @@ -932,6 +982,11 @@ func (v *Viper) find(lcaseKey string) interface{} { if val != nil { return val } + val = v.searchMapWithArrayPrefix(v.config, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.config) != "" { return nil }