16

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?

Mopparthy Ravindranath
  • 3,014
  • 6
  • 41
  • 78

2 Answers2

30

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
Niels Lohmann
  • 2,054
  • 1
  • 24
  • 49
  • 1
    Is there a better way since 2017? In fact, I'm interested in DOM-like traversal of the tree where I use keys to decide about further recursive descent. However, can't call key() on array on certain types of nodes and can't easily check for iterator. – user1552175 Sep 29 '18 at 23:11
  • 5
    You may want to try the [`items()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_afe3e137ace692efa08590d8df40f58dd.html#afe3e137ace692efa08590d8df40f58dd) function. – Niels Lohmann Sep 30 '18 at 09:16
  • 1
    Is there a way to access a node’s parent as you traverse the nodes (items) in the json tree? – DolphinDream Oct 09 '20 at 16:48
  • Not unless you store it while traversing. – Niels Lohmann Oct 09 '20 at 16:50
  • I've provided the solution for accessing the parents in my answer. – BuvinJ Aug 11 '22 at 15:55
0

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"
    "} " );
BuvinJ
  • 10,221
  • 5
  • 83
  • 96
  • This implementation doesn't really seem better than the one that the literal author of the library posted (Niels Lohmann, e.g. `nlohmann`). `std::list` is generally terrible due to pointer-chasing, and `std::function` is an allocating function that can lead to worse performance than the `recursive_iterate` implementation. – Human-Compiler Aug 17 '22 at 00:40
  • Thank you for your notes. I would like to improve this. Niel's posted solution does not provide the parent keys. In the comments, he basically says for people figure that for themselves. That's the point of this. Instead of std::list, what type of collection do you suggest? I was just keeping it simple. And why would that really matter in the context of parsing json? How deep of a nesting should we worry about? That list isn't too likely to have more than a handful of items, and one isn't typically iterating over all that many json entries. – BuvinJ Aug 17 '22 at 13:43
  • Also what type would you recommend I use here instead of std::function? – BuvinJ Aug 17 '22 at 13:44