c++ nlohmann json - how to iterate / find a nested object

26k views Asked by At

I am trying to iterate over a nested json, using nlohmann::json. My json object is below:

{
    "one": 1,
    "two": 2
    "three": {
        "three.one": 3.1
    },
}

I am trying to iterate and /or find nested objects. But, it seems there is no default support for it. It looks like I have to iterate over each sub-object by creating another loop, or call the fn recursively for every sub-object.

My following piece of code, and its result indicate, that only top level iteration possible.

void findNPrintKey (json src, const std::string& key) {
  auto result = src.find(key);
  if (result != src.end()) {
    std::cout << "Entry found for : " << result.key() << std::endl;
  } else {
    std::cout << "Entry not found for : " << key << std::endl ;
  }
}


void enumerate () {

  json j = json::parse("{  \"one\" : 1 ,  \"two\" : 2, \"three\" : { \"three.one\" : 3.1 } } ");
  //std::cout << j.dump(4) << std::endl;

  // Enumerate all keys (including sub-keys -- not working)
  for (auto it=j.begin(); it!=j.end(); it++) {
    std::cout << "key: " << it.key() << " : " << it.value() << std::endl;
  }

  // find a top-level key
  findNPrintKey(j, "one");
  // find a nested key
  findNPrintKey(j, "three.one");
}

int main(int argc, char** argv) {
  enumerate();
  return 0;
}

and the output:

ravindrnathsMBP:utils ravindranath$ ./a.out 
key: one : 1
key: three : {"three.one":3.1}
key: two : 2
Entry found for : one
Entry not found for : three.one

So, is there a recursive iteration available, or do we have to do this ourselves, using is_object() method?

3

There are 3 answers

5
Niels Lohmann On BEST ANSWER

Indeed, iteration does not recurse and there is no library function for this (yet). What about:

#include "json.hpp"
#include <iostream>

using json = nlohmann::json;

template<class UnaryFunction>
void recursive_iterate(const json& j, UnaryFunction f)
{
    for(auto it = j.begin(); it != j.end(); ++it)
    {
        if (it->is_structured())
        {
            recursive_iterate(*it, f);
        }
        else
        {
            f(it);
        }
    }
}

int main()
{
    json j = {{"one", 1}, {"two", 2}, {"three", {"three.one", 3.1}}};
    recursive_iterate(j, [](json::const_iterator it){
        std::cout << *it << std::endl;
    });
}

The output is:

1
"three.one"
3.1
2
2
Elias On

Based on previous answers, this is testsed:

typedef std::list<std::string> json_keys;
typedef std::function< void(const json_keys& key, const json& it)> json_recursive_iterate_callback;

void json_recursive_iterate(const json& js, json_keys& keys, json_recursive_iterate_callback cb)
{
    for (auto& [key, val] : js.items())
    {
        keys.push_back(key);

        if (val.is_structured())
            json_recursive_iterate(val, keys, cb);          
        else
            cb(keys, val);          

        keys.pop_back();
    }
}

Use it like this:

json_keys keys;
json_recursive_iterate(body, keys, [&](const json_keys& keys, const json& it)
{               
    std::string key;            
    for (auto k : keys) key.append("/" + k);

    std::cout << key << " = " << it << std::endl;
}); 
3
BuvinJ On

This is a spin off from the accepted answer, which gives you the added benefit of having the parent keys (in addition to the iterator) as you "walk" the json tree.

The parent keys are provided in a list format to easily iterate over them directly. I've also provided the means to convert that list of strings into a "nested json key" (i.e. a json_pointer). That is an object you can use to directly access that k/v pair when performing assorted operations built into nlohmann::json.

Utility functions

#include <string>
#include <list>

#include "nlohmann.hpp"

using JsonIter = nlohmann::json::const_iterator;

typedef std::list<std::string> JsonKeys;

std::string toJsonStringKey( const JsonKeys &keys )
{
    static const std::string JSON_KEY_DELIM( "/" );
    std::string s;
    for( auto k : keys ) s.append( JSON_KEY_DELIM + k );
    return s;
}

nlohmann::json::json_pointer toJsonPointerKey( const JsonKeys &keys )
{ return nlohmann::json::json_pointer( toJsonStringKey( keys ) ); }

nlohmann::json::json_pointer toJsonPointerKey(
    const JsonKeys &parentKeys, JsonIter it )
{
    JsonKeys allKeys( parentKeys );
    allKeys.push_back( it.key() );
    return nlohmann::json::json_pointer( toJsonStringKey( allKeys ) );
}

typedef std::function< void( const JsonKeys &parentKeys,
    nlohmann::json::const_iterator it )> WalkJsonCallBack;
void walkJson( const nlohmann::json &jsonObj, JsonKeys &parentKeys,
    WalkJsonCallBack callback )
{
    for( auto it( jsonObj.begin() ); it != jsonObj.end(); ++it )
    {
        if( it->is_structured() )
        {
            parentKeys.push_back( it.key() );
            walkJson( *it, parentKeys, callback );
            parentKeys.pop_back();
        }
        else callback( parentKeys, it );
    }
}

Example implementation

const nlohmann::json parsed( nlohmann::json::parse( raw ) ); 
JsonKeys parentKeys;
walkJson( parsed, parentKeys, []( const JsonKeys &parentKeys, JsonIter it )
{
    // INSERT YOUR CODE HERE

    // Example of getting a pointer key..
    const auto key( toJsonPointerKey( parentKeys, it ) );
    // Now, do whatever with that key...

});

Sample Data

And here's the op's sample data, after adding a few more fields and nestings:

const std::string testData(
    "{  \"one\" : 1 , \"two\" : 2, "
       "\"three\" : { "
        " \"three.one\" : 3.1, "
        " \"three.two\" : { \"three.two.one\" : 3.21, \"three.two.two\" : 3.22 }, "
        " \"three.three\": { \"three.three.one\" : 3.31, \"three.three.two\" : 3.32 }, "
        " \"three.four\": 3.4, "
        " \"three.five\": { \"three.five.one\" : 3.51, \"three.five.two\" : 3.52 } "
        "}, "
        "\"four\" : 4"
    "} " );