ju.
ju.

Reputation: 344

Python/JSON: Merge default and user configuration

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

Answers (1)

Alex Martelli
Alex Martelli

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

Related Questions