-
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
Nested maps #195
Nested maps #195
Conversation
f2dc934
to
bf1de71
Compare
[EDIT: outdated list, see this post for the up-to-date version] PR rebased onto
It should now be ready for merge, but feel free to comment... |
Am I understanding this PR right. It will address issues when you have code like this: type Config struct {
Port int
LogConfig struct {
Level string
} `mapstructure:"log_config"`
}
func LoadConfig()(*Config, error) {
viper.SetEnvPrefix("NF")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
config := new(Config)
if err := viper.Marshal(config); err != nil {
return nil, err
}
return config, nil
} and let it be overriden by env vars like |
Hi @rybit, You got it right, that's the idea for different contexts (overrides and flags). I didn't deal with env vars since I don't have a use for them, but that's interesting too. I've tried to execute your code sample, and unfortunately the answer is no, it doesn't work... I'm investigating this and I'll let you know if it can be fixed easily. |
bf1de71
to
9141a0a
Compare
I've added a new commit It now works with both flags and environment variables, and probably everything else, since @rybit your code sample now works, just make sure to use |
This is great! I have been poking at it! Before I have been using a (terrible) method to walk the structure and populate the fields by calling the 'GetXXX' methods. But it did mean that you loaded each value, including zero values. I was hoping that you wouldn't have to set defaults, that it would just return the zero value or the overridden value. It means you can add a value to the struct and it will get the zero or the right override. For instance, if you're setting defaults in the pflag declaration. Or if you're going to default to a zero value. Thanks for the work though! I am excited for when this goes in :) |
I'll try to take a look at this soon, could you rebase the branch in the meantime? |
Great, thanks! The rebase is complete, however the test you recently added fails: --- FAIL: TestShadowedNestedValue (0.00s)
Error Trace: viper_test.go:923
Error: Not equal: "" (expected)
!= "polyester" (actual) (oh, and by the way, it seems you have used the wrong parameter order for Indeed, I followed the convention written in the README file, which stipulates that:
i.e., since This test worked previously (before my commits), because the whole “nested maps” stuff was buggy. My changes make I have two options to choose from before patching and pushing:
What should I do? I'd go for solution 1, which has a much lower impact on the package (and on its possible usages). |
You bring up a good point, I'll try to get some feedback about this, since I can't decide this on my own. |
It's hard to argue with the README, but after getting some feedback, it seems my initial understanding is correct and the documentation should probably be updated. Basically, the precedence should overwrite at times and replace at times. Quoting the response from @spf13 :
If I specify in the config:
then the final result would be
If I specify in the config:
then the final result would be:
So this means that solution 2 would be the correct approach. Apologies about the confusion. |
No problem, that was my first understanding too, until I saw the README :-) However, that means changing the Thanks for the clarification anyway, in the end it will probably more logical that way. |
9141a0a
to
3b8c1c9
Compare
Now, this is finally looking good! To make it short, I've made a bunch of fixes, with 2 major improvements:
README has been updated, too, according to our discussion, and should now be consistent with this implementation. More detailed overview of the most significant changes:
From what I've seen, it is very likely that these patches solve the following issues too: #183, #188, #234, #237 I hope this can be merged shortly, it's becoming harder and harder to rebase… So please let me know if I can be of any help. Thanks a lot! |
Please merge this Pull Request, I could also need this. I guess it will also fix missing Environment variable overrides in |
Removed extra "Aliases"
If foo.bar is defined but foo.bar.baz isn't, then v.Get("foo.bar.baz") returns nil. v.searchMap() modified for this purpose.
So that nested keys are lowercased
overrides (elements added with Set()) should have highest priority, checked first (was after PFlags). From README.md: > Viper uses the following precedence order. Each item takes precedence > over the item below it: > • explicit call to Set > • flag > • env > • config > • key/value store > • default + fix TestBindPFlags() to use a new Viper instance (otherwise, "port" was defined in an earlier test function with Set() and took precedence)
if config["foo"]["bar"] and config["foo.bar"] coexist, the latter value should be used (as described in README).
"clothing.jacket.price" exists in defaults map, but "clothing.jacket" exists and is a value in config map => should remain undefined (“shadowed” by the config)
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.
If a property name contains a ".", it is split and generates sub-maps in the "config" map, as for the other configuration sources. Tests updated accordingly, to reflect these nested values.
Rewriting of find(), which now checks for in-depth values, and ignores shadowed ones. * When looking for "foo.bar": - before: if "foo" was defined in some map, "foo.*" was looked for only in this map - now: "foo.*" is looked for in all maps, the first one with a value is used (even if "foo" was defined elsewhere, in a higher-priority map). * Also, find() tests that the requested key is not shadowed somewhere on its path. e.g., {"foo.bar": 1} in the config map shadows "foo.bar.baz" in the defaults map * Lastly, if in the config map, config["foo"]["bar"] and config["foo.bar"] coexist, the latter value is used. (this should not be necessary for other maps, since by construction their keys should not contain dots) => Get() and IsSet() corrected and simplified, since all the logic about value retrieval has been put in find() + README.md modified accordingly: In Section “Accessing nested keys”, to reflect the corrected behavior of find(): - paths searched for at all levels, not only the one of the first sub-key; - paths may be shadowed by a shorter, higher priority, path. + tests modified accordingly
…d values * Function AllKeys() now returns all keys holding a value (for nested values, the nested key is the full path, i.e., a sequence of dot-separated keys). Previously, returned only depth-1 keys, as well as flags and environment variables: this is more generic and may be used widely. Besides, it takes into account shadowed keys (key ignored if shadowed by a path at a higher-priority level). * Function AllSettings() now returns nested maps for all keys holding a value, as specified by AllKeys(). The value stored in the map is the one with highest priority, as returned by the Get() function (taking into account aliases, environment variables, flags, etc.). This fixes Unmarshal(): it fills in correct values for nested configuration elements overridden by flags or env variables. + tests fixed accordingly + test added to TestShadowedNestedValue(), to test Unmarshalling of shadowed keys
892b80c
to
74f9cd8
Compare
I will review this tomorrow and see if I can merge right away. |
I checked the tests and most of the code. I also used this version in a private project and the improved behaviour is a bliss. I will go ahead and merge now. One comment I have is whether setting a key to two different maps should maybe merge them instead of replacing the first one with the second one. This is of course a question of semantics and a design decision; I just feel it would possibly make more sense, as there is no Thanks a lot for the great work. |
@benoitmasson @awishformore this pull requests breaks Does not work:
Does work:
|
OK @arekkas, I'll have a look. |
This PR should fix #168 and #190, among others, by updating the
Set*()
functions so that they change “deep” values in the configuration maps.Not ready for merge: no code yet, only unit tests in file
overrides_test.go
(corresponding to the cases discussed in #168), to make sure we agree on the final behavior.Please comment if you wish to change anything.