12

I am using jsoncpp to read settings from a JSON file.

I would like to have two cascading settings file, say MasterSettings.json and LocalSettings.json where LocalSettings is a subset of MasterSettings. I would like to load MasterSettings first and then LocalSettings. Where LocalSettings has a value that differs from MasterSettings, that value would overwrite the one from MasterSettings. Much like the cascade in CSS.

Is there any elegant way to do this with jsoncpp?

Tim MB
  • 4,413
  • 4
  • 38
  • 48
  • 1
    Worth noting: if localSettings.json is still an unparsed, raw JSON string, `reader.parse(LocalSettingsStr, MasterSettings)` will simultaneously parse the raw string and merge it into MasterSettings overwriting as required. And if you already have it as JSON and absolutely don't care about performance but want simplicity, `reader.parse(writer.write(LocalSettings),MasterSettings);` is a one-liner that will do the whole operation albeit in an awfully roundabout and costly way. – SF. Dec 19 '22 at 15:41

2 Answers2

15

I'm going to assume your settings files are JSON objects.

As seen here, when JSONCpp parses a file, it clears the contents of the root node. This mean that trying to parse a new file on top of the old one won't preserve the old data. However, if you parse both files into separate Json::Value nodes, it's straight forward to recursively copy the values yourself by iterating over the keys in the second object using getMemberNames.

// Recursively copy the values of b into a. Both a and b must be objects.
void update(Json::Value& a, Json::Value& b) {
    if (!a.isObject() || !b.isObject()) return;

    for (const auto& key : b.getMemberNames()) {
        if (a[key].isObject()) {
            update(a[key], b[key]);
        } else {
            a[key] = b[key];
        }
    }
}
user35147863
  • 2,525
  • 2
  • 23
  • 25
  • 3
    This will fail on the `if(a[key].isObject()) {` for keys not present in `a`. Referring to non-existent key instantiates it with value of `nullValue`, and isObject() returns true on nullValue. That way you'll recursively call update on a=null, and the copying won't occur. (also, Object type `a[key]` won't be overwritten by non-object `b[key]`.) What you need to do is `if(a[key].type() == Json::objectValue && b[key].type() == Json::objectValue) {` - only then perform recursive merge, otherwise just overwrite `a[key]` value. – SF. Feb 04 '16 at 12:21
  • Changing the variable types to QJson, in Qt5, seems to work. – Checo R Jun 05 '17 at 17:54
  • Is it not possible to move `b[key]` into `a[key]`? – pooya13 Jan 01 '21 at 22:37
  • @pooya13 Merge `a = { "k": { "x": 1 } }` with `b = { "k": { "y": 2 } }` moving instead of recursing. Expected result: `a = { "k": { "x": 1, "y": 2 } }`. Actual result: `a = { "k": { "y": 2 } }` – SF. Jan 11 '23 at 13:02
1

I know it has been a while. but...

In addition to the correct answer and the commentary, here is a code version for those who use a older g++ version:

void jsonMerge(Json::Value &a, Json::Value &b) {                                                                        
                                                                                                                  
   if (!a.isObject() || !b.isObject()) return;                                                                           
                                                                                                                    
   vector<string> member_name = b.getMemberNames();                                                                      
   string key = "";                                                                                                      
   for (unsigned i = 0, len = member_name.size(); i < len; i++) {                                                        
       key = member_name[i];                                                                                               
                                                                                                                    
       if (!a[key].isNull() && a[key].type() == Json::objectValue && b[key].type() == Json::objectValue) {                 
           jsonMerge(a[key], b[key]);                                                                                        
       } else {                                                                                                            
           a[key] = b[key];                                                                                                  
       }                                                                                                                   
   }                                                                                                                     
   member_name.clear();                                                                                                  
}
Paul Floyd
  • 5,530
  • 5
  • 29
  • 43