Search code examples
c++nlohmann-json

How do I wrap nlohmann json objects with additional layers of keys from a vector of key names?


I have a vector of key names, how do I create a json of these nested keys, such that it's like:

std::vector<std::string> keys = {"a", "b", "c"};

json myJson = createJson (keys);

the resulting json I want

{
    "a" : {
        "b" : {
            "c" : {
            }
        }
    }
}

and then after that, I want to be able to modify the deepest layer of myjson later in code, equivalent to:

myJson["a"]["b"]["c"].merge_patch(newJson1)
myJson["a"]["b"]["c"].merge_patch(newJson2)

However, the size of my key vector is variable, so I can't hardcode the ["a"]["b"]["c"] part, I basically need some way to for loop through each key until I reach the end, and then do my update on that last element.

So far I solved createJson by looping through the keys in reverse and just creating new jsons, but this feels very inefficient as it looks like I'm creating a bunch of new jsons and a lot of copys:

json createJson (const std::vector<std::string> &keys) {
    json j = json::value_t::object;
    for (auto it = keys.rbegin(); it != keys.rend(); it++) {
        json temp;
        temp[*it] = j;
        j = temp;
    }
    return j;
}

But now I don't have access to the deepest layer of the json anymore. What I then did is:

json createJson (const std::vector<std::string> &keys, json & j) {
    for (auto it = keys.rbegin(); it != keys.rend(); it++) {
        json temp;
        temp[*it] = j;
        j = temp;
    }
}

json j;
createJson(newJson1);
createJson(newJson2);
j.merge_patch(newJson1);
j.merge_patch(newJson2);

Which works, but seems kind of dumb because now instead of just modifying the deepest json nest, merge_patch is merging across all the keys. Is there a better way to do this? Maybe my createJson should also return some pointer to the last nesting location somehow?


Solution

  • This implementation of createJson might be a bit more efficient:

    nlohmann::json createJson(const std::vector<std::string>& keys)
    {
        nlohmann::json root;
        nlohmann::json* it = &root;
        for (auto& key : keys)
        {
            it = &((*it)[key]);
        }
        *it = nlohmann::json({});
        return root;
    }
    

    If you want access to the last created element you could just return it from createJson, note that in order for the pointer to remain valid we must pass in the root element otherwise returning it from the function will invalidate the pointer:

    nlohmann::json* createJson(nlohmann::json& root, const std::vector<std::string>& keys)
    {
        nlohmann::json* it = &root;
        for (auto& key : keys)
        {
            it = &((*it)[key]);
        }
        *it = nlohmann::json({});
        return it;
    }
    

    Then you can do:

    nlohmann::json myJson;
    auto it = createJson(myJson, keys);
    it->merge_patch(newJson1);