3

I have been trying for too much time now, to access a json_reader ptree from the boost library.

I have a json file that is capsulated really often: (pseudo-json:)

"Foo": {
  "nameofFoo:"foofoo"
  "Bar": [{
    "BarFoo": 
      { BarFooDeep: { 
           BarFooDeepDeep: { 
            "BarFooValue1": 123 
            "BarFooValue2" : 456
          }
        }
      }
     "FooBar": [ {
        "FooBarDeep" :[ {
           FooBarDeepDeep:[ {
              FooBarValue1: "ineedthis"
              FooBarValue2: "andthis"
              } ]
           FooBarDeepDeep1:[ {
              FooBarValue1: "ineedthis"
              FooBarValue2: "andthis"
              } ]
        "FooBarDeep" :[ {
           FooBarDeepDeep2:[ {
              FooBarValue1: "ineedthis"
              FooBarValue2: "andthis"
              } ]
           FooBarDeepDeep3:[ {
              FooBarValue1: "ineedthis"
              FooBarValue2: "andthis"
              } ]
and so on .... won t complete this now...

Now I only need to get FooBarValue1 and FooBarValue2 of all FooBar.

I know ptree puts arrays together with empty childs ("")

I can access all the members by itereration over all the childs recursively.

But is there not a better way to access special values?

how does ptree find works? i always get compiler errors ...

ptree jsonPT;
read_json( JSON_PATH, jsonPT);
ptree::const_iterator myIT = jsonPT.find("FooBarValue1");
double mlat = boost::lexical_cast<int>(myIT->second.data());

error: conversion from ‘boost::property_tree::basic_ptree, std::basic_string >::assoc_iterator’ to non-scalar type ‘boost::property_tree::basic_ptree, std::basic_string >::const_iterator’ requested ptree::const_iterator myIT = jsonPT.find("FooBarValue1");

Can anyone give me a useful hint how to get access to this ptree?!?

Druide
  • 48
  • 1
  • 6
  • can you please make the JSON sensible? It's too broken to say anything really – sehe May 07 '15 at 19:14
  • You can look at the path algorithm here for inspiration http://stackoverflow.com/a/29199812/85371 (the processing is identical for JSON, since it just works on ptrees) – sehe May 07 '15 at 19:35
  • The edit didn't make the json valid (not even close). I've done it for you, see my answer :/ – sehe May 08 '15 at 13:40

2 Answers2

5

As hinted in the linked answer I commented (Boost.PropertyTree subpath processing), you could write your own "selector" query, so you could write stuff like:

read_json("input.txt", pt);

std::ostream_iterator<std::string> out(std::cout, ", ");

std::cout << "\nSpecific children but in arrays: ";
enumerate_path(pt, "Foo.Bar..FooBar..FooBarDeep1..FooBarDeepDeep6..FooBarValue2", out);

std::cout << "\nSingle wildcard: ";
enumerate_path(pt, "Foo.Bar..FooBar..FooBarDeep1..*..FooBarValue2", out);

std::cout << "\nTwo wildcards: ";
enumerate_path(pt, "Foo.Bar..FooBar..*..*..FooBarValue2", out);

The enumerate_path function need not be too complicated and takes any output iterator (so you can back_inserter(some_vector) just as well):

template <typename Tree, typename Out, typename T = std::string>
Out enumerate_path(Tree const& pt, typename Tree::path_type path, Out out) {
    if (path.empty())
        return out;

    if (path.single()) {
        *out++ = pt.template get<T>(path);
    } else {
        auto head = path.reduce();
        for (auto& child : pt) {
            if (head == "*" || child.first == head) {
                out = enumerate_path(child.second, path, out);
            }
        }
    }

    return out;
}

As simple working demo prints:

Specific children but in arrays: andthis6, 
Single wildcard: andthis6, andthis7, andthis8, andthis9, 
Two wildcards: andthis1, andthis2, andthis3, andthis4, andthis6, andthis7, andthis8, andthis9, 

That is with the following input.txt:

{
    "Foo": {
        "nameofFoo": "foofoo",
        "Bar": [{
            "BarFoo": {
                "BarFooDeep": {
                    "BarFooDeepDeep": {
                        "BarFooValue1": 123,
                        "BarFooValue2": 456
                    }
                }
            },
            "FooBar": [{
                "FooBarDeep0": [{
                    "FooBarDeepDeep1": [{
                        "FooBarValue1": "ineedthis1",
                        "FooBarValue2": "andthis1"
                    }],
                    "FooBarDeepDeep2": [{
                        "FooBarValue1": "ineedthis2",
                        "FooBarValue2": "andthis2"
                    }]
                },
                {
                    "FooBarDeepDeep3": [{
                        "FooBarValue1": "ineedthis3",
                        "FooBarValue2": "andthis3"
                    }],
                    "FooBarDeepDeep4": [{
                        "FooBarValue1": "ineedthis4",
                        "FooBarValue2": "andthis4"
                    }]
                }],
                "FooBarDeep1": [{
                    "FooBarDeepDeep6": [{
                        "FooBarValue1": "ineedthis6",
                        "FooBarValue2": "andthis6"
                    }],
                    "FooBarDeepDeep7": [{
                        "FooBarValue1": "ineedthis7",
                        "FooBarValue2": "andthis7"
                    }]
                },
                {
                    "FooBarDeepDeep8": [{
                        "FooBarValue1": "ineedthis8",
                        "FooBarValue2": "andthis8"
                    }],
                    "FooBarDeepDeep9": [{
                        "FooBarValue1": "ineedthis9",
                        "FooBarValue2": "andthis9"
                    }]
                }]
            }]
        }]
    }
}

Live On Coliru

Full Listing

Live On Coliru

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <iostream>

template <typename Tree, typename Out, typename T = std::string>
Out enumerate_path(Tree const& pt, typename Tree::path_type path, Out out) {
    if (path.empty())
        return out;

    if (path.single()) {
        *out++ = pt.template get<T>(path);
    } else {
        auto head = path.reduce();
        for (auto& child : pt) {
            if (head == "*" || child.first == head) {
                out = enumerate_path(child.second, path, out);
            }
        }
    }

    return out;
}

int main() {

    std::ostream_iterator<std::string> out(std::cout, ", ");
    using namespace boost::property_tree;

    ptree pt;
    read_json("input.txt", pt);

    std::cout << "\nSpecific children but in arrays: ";
    enumerate_path(pt, "Foo.Bar..FooBar..FooBarDeep1..FooBarDeepDeep6..FooBarValue2", out);

    std::cout << "\nSingle wildcard: ";
    enumerate_path(pt, "Foo.Bar..FooBar..FooBarDeep1..*..FooBarValue2", out);

    std::cout << "\nTwo wildcards: ";
    enumerate_path(pt, "Foo.Bar..FooBar..*..*..FooBarValue2", out);
}
Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Wow! thx that looks really great and amazing! I ll try to integrate that in my program! – Druide May 11 '15 at 07:06
  • Thx for cleaning up the broken JSON, i also forgot to tell that for every FooBarDeep is a FooBarDeepName Well that way i can get all Data from one Key, is there also a good option to get them for a Value in a Key? All my FooBarDeep are FooBarDeep and i have (n) of them, only the FooBarDeepName = "alwaysdifferent" now i need to read the FooBarDeepName and if its "NameOfFoo_I_want" Write the stuff below in a vector or something similar. I think i have to ask the People who send me this JSON why its made that stupid way. – Druide May 11 '15 at 10:59
  • @Druide Please. Just post a new question? With a 7 line demo JSON. Comments don't work this way, and you can't really invalidate existing answers by changing the question. I'll have a look if you post it as a clearer question. – sehe May 11 '15 at 12:20
  • Oh and please see http://meta.stackoverflow.com/questions/5234/how-does-accepting-an-answer-work – sehe May 11 '15 at 12:20
2

find() is for retrieving a child node by key; it doesn't search the whole ptree. It returns an assoc_iterator (or const_assoc_iterator), which you can convert to an iterator via the to_iterator() method on the parent:

ptree::const_assoc_iterator assoc = jsonPT.find("FooBarValue1");
ptree::const_iterator myIT = jsonPT.to_iterator(assoc);

To search the ptree, you'll need to iterate it recursively:

struct Searcher {
    struct Path { std::string const& key; Path const* prev; };
    void operator()(ptree const& node, Path const* path = nullptr) const {
        auto it = node.find("FooBarValue1");
        if (it == node.not_found()) {
            for (auto const& child : node) {  // depth-first search
                Path next{child.first, path};
                (*this)(child.second, &next);
            }
        } else {    // found "FooBarValue1"
            double mlat = boost::lexical_cast<int>(myIT->second.data());
            // ...
            std::cout << "Mlat: " << mlat << std::endl;
            std::cout << "Path (reversed): ";
            for (Path const* p = path; p != nullptr; p = p->prev)
                std::cout << p << ".";
            std::cout << std::endl;
        }
    }
};
Searcher{}(jsonPT);

Alternatives for writing the recursive traversal would be a C++14 generic lambda, or in C++11 a type-erased concrete lambda using std::function.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Thank you! Hoped there is a option to not do it recursively. I ll do it now that way, and hope to get the correct one. If i do a node.find("FooBarValue1"); i get all FooBarValue1 to mlat. How do i know if its from FooBarDeepDeep1 or 2 or 3? – Druide May 08 '15 at 06:36
  • @Druide you can keep a record of the path taken to reach a particular node as you perform the recursive search. – ecatmur May 08 '15 at 10:50
  • @ecatumur I need it in c++11 and at the moment i don t get the auto rec converted in my function to work ... Dunno maybe i need my weekend to be clear in my head ... at the moment i have the feeling i m not understanding anything anymore :/ – Druide May 08 '15 at 12:03
  • @Druide changed to the classic functor approach. – ecatmur May 08 '15 at 18:02