Home > Net >  building a nested JSON
building a nested JSON

Time:04-06

Some data files that I need to read / parse have headers in the style:

level0var = value0
level0var.level1field = value1 
level0var.level1array[11].level2field = value2
...

In other words, they look like nested C-style structs and arrays, but none of these are declared in the header: I need to infer the structure as I read.

My plan was to use the famous nlohmann::json library to store this, because its flexibility would allow me to change the structure of the data during parsing, and save the header in a more readable form.

I read the assignments in as lhs = rhs, and both of those are strings. Given json header;, to deal with the unknown, variable depth of the structures I want to do something like

// std::string lhs = "level0var.level1field.level2field";
// std::string rhs = "value2"; 
auto current_level = header;
while ( lhs != "" ) {
   auto between = lhs.substr ( 0, lhs.find_first_of ( "." ) );
   lhs.erase ( 0, lhs.find_first_of ( "." )   1 );
   if ( lhs == between ) break;
   current_level [ between ] = json ( {} );
   current_level = current_level [ between ];
}
current_level = rhs;
std::cout << std::setw(4) << header;

for every line that has at least 1 struct level (leaving the arrays for now).

The strange thing is that using this loop, the only thing the last line returns is null, whereas when I use

header [ "level0var" ] [ "level1field" ] [ "level2field" ] = rhs;
std::cout << std::setw(4) << header;

it gives the expected result:

{
    "level0var": {
        "level1field": {
           "level2field": "value2"
        } 
    }
}

Is there a way to build this hierarchical structure iteratively (without supplying it as a whole)? Once I know how to do structs, I hope the arrays will be easy!

The example I made at work does not run on coliru (which does not have the JSON library I guess).

CodePudding user response:

I managed to achieve what I understood you wanted with this code:

using namespace nlohmann;

void main()
{
    json header;
    std::string lhs = "level0var.level1field.level2field";
    std::string rhs = "value2"; 
    json * current_level = &header;
    while (lhs != "") {
        auto between = lhs.substr(0, lhs.find_first_of("."));
        lhs.erase(0, lhs.find_first_of(".")   1);
        (*current_level)[between] = json({});
        current_level = &((*current_level)[between]);
        if (lhs == between) break;
    }
    *current_level = rhs;
    std::cout << std::setw(4) << header;
}

A few notes:

  1. I haven't managed to use nlohmann::json_pointer although it seemed useful at first. Instead I used a "normal" raw pointer. Some pointer semantics is required in order to refer to the exiting json structure you have.

  2. I moved the condition for exiting the loop to the end of the loop (otherwise the innermost level was missing).

  3. To be honest, I am not sure my solution is the best one. Messing with raw pointers in this case is something that has to be done very carefully. But you can try it if you like.

CodePudding user response:

This is indeed trivial to do with the json_pointer, using the correct operator[] overload and the json_pointer::get_unchecked() function that already does all this work for you.

The only effort is to convert your .-separated key into the /-separated path it expects.

#include <nlohmann/json.hpp>

#include <algorithm>
#include <iostream>
#include <string>

using json = nlohmann::json;

std::string dots_to_path(std::string key)
{
    std::replace(key.begin(), key.end(), '.', '/');
    key.insert(0, 1, '/');
    return key;
}

int main() {

    json header;

    std::string lhs = "level0var.level1field.level2field";
    std::string rhs = "value1";

    json::json_pointer key{dots_to_path(lhs)};
    header[key] = rhs;
    std::cout << std::setw(4) << header;
}
  • Related