-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Overriding nested keys #168
Comments
I think the same problem exists with |
I'm currently investigating this issue, which seems to be related to mine (issue #190), in order to complete a PR. The overriding functions ( Given this config: level1a:
value1
level1b:
level2:
value2
My feeling is that it should raise errors (not yet in the prototype of the function however, but I can add it). Another option would be to replace the value by the map, or conversely, or to work out something more complex where both would cohabit, but I find this risky for the end-user. What's your (and @spf13's) opinion on that? |
In other languages (Perl for example) you would get exception in first case and second one will override the value which is acceptable I think. If we decide to throw errors in both cases it will be fine too. Only drawback of this is that one would not be able to recover from this situation unless some |
It's a bit more complex than the Perl suggestion because of the merging of values across various data sources in an ordered way. There's a bunch of scenarios to consider and we need to be very thoughtful and consistent in our approach. I wrote out a few scenarios.. but then realized really there's just a few fundamental questions that need to be addressed. I'm not certain that this is a complete list, but it's a good start. What should the correct behavior be when:
My thoughts around these are:
I don't think the functions (set, setDefault, etc) should trigger errors in these conditions. These aren't errors, but a normal function of configuration with completely valid usages. If a specific key expects a specifically shaped value then the app should error when that condition isn't met, but that's for the application to determine, not Viper. I'd love feedback on these guidelines and I think it would be a good next step to come up with practical scenarios and see if they hold. |
@spf13 What is the difference between Your proposal sounds good but I have concerns about fourth case. Imagine situation like this: Base ---
web:
ports:
- 8080
- 9500
- 9090 Overlay ---
web:
ports:
- 80 According to fourth it may end up like this (unless array is treated as ---
web:
ports:
- 8080
- 9500
- 9090
- 80 So you might treat arrays as simple Edit: Actually it should be as intuitive as merging two JSON files: https://jsfiddle.net/rhwmvta1/ :) |
I understand that you find it reasonable that a user “replaces” anything in a map by anything, that's fine with me. As for your 4 behaviors: But that's when you Set(level1.level2, value1)
Set(level1, value2) (or the converse)? In this case the same map is modified. Finally, what should |
@harnash yes, that is what I meant by those two terms. Your example is a good one as it highlights an overall issue with this or any approach. I didn't really consider arrays in my above rules.
are clearly different, the first would be a key:value where the second is an array. We need an additional rule to handle arrays: 5. Arrays will be joined So if the overlay is
The merge would be
But if the overlay is
The merge would be
This does create a problem though as in some methods these overlay settings are ambiguous... or only permit single values, not arrays. ENV_WEB__PORTS=80 These methods do provide distinct settings for a map/k:v vs value Base
Overlay
The merge would be
All methods work with maps well. ENV_WEB__PATH="/srv/www/" |
I'm not sure I agree with that. Let's use a scenario to make sure we are talking about the same behavior. Config:
Flag: What should Get("web.port") return?
I think it should be the zero/nil value (depending on type). |
I agree with that, that's what I meant. Actually you're right, reasoning on examples will make it easier to understand each other (and will give us plenty of test cases ;-) ) So if I reformulate the previous behaviors as examples, to make sure we agree on everything (excluding the obvious base case where the base value is overridden by a value): Case 1. (An overlay layer has a value where a base layer has a key:value)SetDefault(level1.level2, value1)
Set(level1, value2)
fmt.Println(Get(level1), Get(level1.level2)) should display We expect the same result for an override in the same layer: Set(level1.level2, value1)
Set(level1, value2) Case 2. (An overlay layer has a key:value where a base layer has a value)SetDefault(level1, value1) // or Set(level1, value1)
Set(level1.level2, value2)
fmt.Println(Get(level1), Get(level1.level2)) should display Case 3&4. (An overlay layer has a map where a base layer has a map/a value)SetDefault(level1.level2a, value1) // or Set(level1.level2a, value1)
Set(level1.level2b, value2)
fmt.Println(Get(level1), Get(level1.level2a), Get(level1.level2b)) should display But maybe you meant this instead? Case 3&4b. (An overlay layer has a map where a base layer has a map)SetDefault(level1.level2a, value1) // or Set(level1.level2a, value1)
Set(level1, map[string]interface{}{"level2b": value2})
fmt.Println(Get(level1), Get(level1.level2a), Get(level1.level2b)) As for me, this should display Set(level1, merge(Get(level1), map[string]interface{}{"level2b": value2})) If we choose to merge maps inside the Case 5. (An overlay layer has a value/a map where a base layer has an array)SetDefault(level1, []int{value1}) // or Set(level1, []int{value1})
Set(level1, value2)
fmt.Println(Get(level1)) should display Case 6. (An overlay layer has an array where a base layer has an array)SetDefault(level1, []int{value1}) // or Set(level1, []int{value1})
Set(level1, []int{value2})
fmt.Println(Get(level1)) Unlike you, and for the same reasons as in Case 3&4b, I'd replace the old array, thus expecting Set(level1, append(Get(level1), []int{value2})) I think this covers everything... we just need to agree on something for cases 3&4b and 6. |
@benoitmasson: I like the idea of writing cases using code and discuss edge cases based on that. We could use that to write Unit Tests later ;-) Unfortunately github is not a great place to work with such examples since we can't easily reference specific lines... How about throwing those cases into some Google Doc or similar when we could add comments, new cases, resolve questions/comments? Or maybe start PR with just Unit Tests already? |
As suggested, I created PR #195 with the above tests, following this global rule: every assignment replaces any existing value (thus, merges have to be explicit). Feel free to comment (in the PR?) if you (dis)agree on anything… |
PR #195 is completed and should have fixed this issue. Let me know if something still goes wrong. |
Fixes #71, #93, #158, #168, #209, #141, #160, #162, #190 * Fixed: indentation in comment * Fixed: Get() returns nil when nested element not found * Fixed: insensitiviseMaps() made recursive so that nested keys are lowercased * Fixed: order of expected<=>actual in assert.Equal() statements * Fixed: find() looks into "overrides" first * Fixed: TestBindPFlags() to use a new Viper instance * Fixed: removed extra aliases from display in Debug() * Added: test for checking precedence of dot-containing keys. * Fixed: Set() and SetDefault() insert nested values * Added: tests for overriding nested values * Changed: AllKeys() includes all keys / AllSettings() includes overridden nested values * Added: test for shadowed nested key * Fixed: properties parsing generates nested maps * Fixed: Get() and IsSet() work correctly on nested values * Changed: modifier README.md to reflect changes
Hey viper team, and thanks for the amazing work on viper !
I just encountered an issue while playing around with viper, it seems to me that overriding values in nested keys doesn't update the parent value:
With a simple nested config:
Outputs:
"oldValue" should be overriden at all levels by Set(), and a Get() should return consistent values.
The text was updated successfully, but these errors were encountered: