C++ nlohmann json - как перебрать / найти вложенный объект

Я пытаюсь перебрать вложенный json, используя nlohmann::json. Мой объект JSON ниже:

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

Я пытаюсь перебрать и / или найти вложенные объекты. Но, похоже, поддержки по умолчанию нет. Похоже, мне приходится перебирать каждый подобъект, создавая другой цикл, или рекурсивно вызывать fn для каждого подобъекта.

Следующий фрагмент кода и его результат показывают, что возможна только итерация верхнего уровня.

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;
}

и вывод:

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

Итак, есть ли рекурсивная итерация, или мы должны сделать это сами, используя метод is_object()?

3 ответа

Решение

Действительно, итерация не повторяется, и для этого пока нет библиотечной функции. Как насчет:

#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;
    });
}

Выход:

1
"three.one"
3.1
2

Это ответвление от принятого ответа, которое дает вам дополнительное преимущество наличия родительских ключей (в дополнение к итератору), когда вы «прогуливаетесь» по дереву json.

Родительские ключи предоставляются в формате списка, чтобы их можно было легко перебирать напрямую. Я также предоставил средства для преобразования этого списка строк во «вложенный ключ json» (т.е.). Это объект, который вы можете использовать для прямого доступа к этой паре k/v при выполнении различных операций, встроенных в nlohmann::json.

Вспомогательные функции

      #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 );
    }
}

Пример реализации

      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...

});

Образец данных

А вот пример данных операции после добавления еще нескольких полей и вложений:

      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"
    "} " );

Основываясь на предыдущих ответах, это проверяется:

      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();
    }
}

Используйте это следующим образом:

      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;
}); 
Другие вопросы по тегам