Reputation: 344
I'm using JSON configuration files in my python application. I want to have a default configuration with hierarchical layers.
The user configuration should only contain parameters which differ from the default.
My current solution is:
# default.json
{
"parA" : 1,
"parB" : {
"sparA" : 0,
"sparB" : 1
}
}`
# user.json
{
"parB" : {
"sparB" : 9
}
}
The configuration is loaded by
default_json = open("default.json")
default_config = json.load(default_json)
default_json.close()
user_json = open("user.json")
user_config = json.load(user_json)
user_json.close()
config = default_config.copy()
config.update(user_config)
The problem with this is, that the whole key "parA" is overwritten, so that "sparA" is deleted. Is there a simple way to overwrite only sub-keys but never overwrite the whole parent key?
Upvotes: 4
Views: 1840
Reputation: 882851
It can be done, though it's a bit delicate. The following assumes structural compatibility (e.g you're not trying to merge a non-dict into a dict, etc) and that in the case of non-dicts the "merge" is a simple replacement.
It also assumes it's OK to modify the base object in-place (it appears to be so since you're updating a copy anyway; if the latter is not the case it's no big deal, just add a copy statement such as base_obj = dict(base_obj)
just before the for
loops)...:
def selective_merge(base_obj, delta_obj):
if not isinstance(base_obj, dict):
return delta_obj
common_keys = set(base_obj).intersection(delta_obj)
new_keys = set(delta_obj).difference(common_keys)
for k in common_keys:
base_obj[k] = selective_merge(base_obj[k], delta_obj[k])
for k in new_keys:
base_obj[k] = delta_obj[k]
return base_obj
This works for your given example, but would break if e.g with your given default.json
, the user.json
were, say, '{"parB": 23}'
(merging a non-dict into a dict violates the structural compatibility constraint; I'm not sure what you'd want to happen in such a case).
So once you've tweaked the function to your exact specs, all you need is to replace, in your given code,
config.update(user_config)
with
config = selective_merge(config, user_config)
Upvotes: 6