From 4190078ffddbbff1d9772c7961007d038b36a7e4 Mon Sep 17 00:00:00 2001 From: Benoit Masson Date: Thu, 2 Jun 2016 15:52:28 +0200 Subject: [PATCH] Fix: Set() and SetDefault() insert nested values Set() and SetDefault() fixed, so that they insert (or substitute) correctly "nested values" (= values identified by a complete path, i.e. containing dots; e.g. "foo.bar"). They scan (and create if needed) intermediate maps, so that the value reaches the innermost map. Examples : * v.override = make(map[string]interface{}) v.Set("foo.bar", 10) v.Set("foo.baz", 20) fmt.Println(v.override["foo"]["bar"]) // displays "10" fmt.Println(v.override["foo"]["baz"]) // displays "20" Furthermore, pre-existing non-map value are erased: * v.Set("foo.bar", 10) v.Set("foo.bar.baz", 20) fmt.Println(v.override["foo"]["bar"]) // displays "map["baz": 20]" The low-level work is performed by function deepSearch(), it scans the given map according to a given key path, creating intermediate maps if necessary. --- util.go | 31 +++++++++++++++++++++++++++++++ viper.go | 16 ++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/util.go b/util.go index 5cc953b..11150a0 100644 --- a/util.go +++ b/util.go @@ -203,3 +203,34 @@ func parseSizeInBytes(sizeStr string) uint { return safeMul(uint(size), multiplier) } + +// deepSearch scans deep maps, following the key indexes listed in the +// sequence "path". +// The last value is expected to be another map, and is returned. +// +// In case intermediate keys do not exist, or map to a non-map value, +// a new map is created and inserted, and the search continues from there: +// the initial map "m" may be modified! +func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { + for _, k := range path { + m2, ok := m[k] + if !ok { + // intermediate key does not exist + // => create it and continue from there + m3 := make(map[string]interface{}) + m[k] = m3 + m = m3 + continue + } + m3, ok := m2.(map[string]interface{}) + if !ok { + // intermediate key is a value + // => replace with a new map + m3 = make(map[string]interface{}) + m[k] = m3 + } + // continue search from here + m = m3 + } + return m +} diff --git a/viper.go b/viper.go index b36048d..6efc2b3 100644 --- a/viper.go +++ b/viper.go @@ -935,7 +935,13 @@ func SetDefault(key string, value interface{}) { v.SetDefault(key, value) } func (v *Viper) SetDefault(key string, value interface{}) { // If alias passed in, then set the proper default key = v.realKey(strings.ToLower(key)) - v.defaults[key] = value + + path := strings.Split(key, v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(v.defaults, path[0:len(path)-1]) + + // set innermost value + deepestMap[lastKey] = value } // Set sets the value for the key in the override regiser. @@ -945,7 +951,13 @@ func Set(key string, value interface{}) { v.Set(key, value) } func (v *Viper) Set(key string, value interface{}) { // If alias passed in, then set the proper override key = v.realKey(strings.ToLower(key)) - v.override[key] = value + + path := strings.Split(key, v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(v.override, path[0:len(path)-1]) + + // set innermost value + deepestMap[lastKey] = value } // ReadInConfig will discover and load the configuration file from disk