问题
With my tree looking like this:
{
"Library":
{
"L_ID": "1",
"Book":
{
"B_ID": "1",
"Title": "Moby Dick"
},
"Book":
{
"B_ID": "2",
"Title": "Jurassic Park"
}
},
"Library":
{
"L_ID": "2",
"Book":
{
"B_ID": "1",
"Title": "Velocity"
},
"Book":
{
"B_ID": "2",
"Title": "Creeper"
}
}
}
What i am looking to do is iterate through the libraries. When i find the L_ID that i am looking for, iterate through the books until i find the B_ID i'm looking for. At that point, i'd like to access all the leaves in that section. I.e. looking for library 2, book 1, title Note: There's likely a better way than this.
boost::property_tree::ptree libraries = config_.get_child("Library");
for (const auto &lib : libraries)
{
if (lib.second.get<uint16_6>("L_ID") == 2)
{
//at this point, i know i'm the correct library...
boost::property_tree::ptree books = lib.get_child("Book");
for (const auto &book : books)
{
if (book.second.get<uint16_t>("B_ID") == 1)
{
std::string mybook = book.second.get<std::string>("Title");
}
}
}
I fail out as soon as i try looking into my first sub tree. What's going wrong here??
回答1:
For starters, the "JSON" is wildly flawed. At least fix the missing quotes and commas:
{
"Library": {
"L_ID": "1",
"Book": {
"B_ID": "1",
"Title": "Moby Dick"
},
"Book": {
"B_ID": "2",
"Title": "Jurassic Park"
}
},
"Library": {
"L_ID": "2",
"Book": {
"B_ID": "1",
"Title": "Velocity"
},
"Book": {
"B_ID": "2",
"Title": "Creeper"
}
}
}
Next up, you seem to be confused. get_child("Library")
gets the first child by that name, not a node containing child nodes called "Library" (that would be the root node, by the way).
May I suggest adding some abstraction, and perhaps some facilities to query by some names/properties:
int main() {
Config cfg;
{
std::ifstream ifs("input.txt");
read_json(ifs, cfg.data_);
}
std::cout << "Book title: " << cfg.library(2).book(1).title() << "\n";
}
As you can see, we assume a Config
type that can find a library:
Library library(Id id) const {
for (const auto& lib : libraries())
if (lib.id() == id) return lib;
throw std::out_of_range("library");
}
What is libraries()
? We'll delve into it deeper, but lets just look at it for a second:
auto libraries() const {
using namespace PtreeTools;
return data_ | named("Library") | having("L_ID") | as<Library>();
}
That magic should be read as "give me all nodes that are named Library, which have a L_ID property but wrap them in a Library object". Skipping on the detail for now, lets look at the Library
object, which apparently knows about books()
:
struct Library {
ptree const& data_;
Id id() const { return data_.get<Id>("L_ID"); }
auto books() const {
using namespace PtreeTools;
return data_ | named("Book") | having("B_ID") | as<Book>();
}
Book book(Id id) const {
for (const auto& book : books())
if (book.id() == id) return book;
throw std::out_of_range("book");
}
};
We see the same pattern in books()
and book(id)
to find a specific item.
The Magic
The magic uses Boost Range adaptors and lurks in PtreeTools
:
namespace PtreeTools {
namespace detail {
// ...
}
auto named(std::string const& name) {
return detail::filtered(detail::KeyName{name});
}
auto having(std::string const& name) {
return detail::filtered(detail::HaveProperty{name});
}
template <typename T>
auto as() {
return detail::transformed(detail::As<T>{});
}
}
That's deceptively simple, right. That's because we're standing on the shoulders of Boost Range:
namespace detail {
using boost::adaptors::filtered;
using boost::adaptors::transformed;
Next, we only define the predicates that know how to filter for a specific ptree node:
using Value = ptree::value_type;
struct KeyName {
std::string const _target;
bool operator()(Value const& v) const {
return v.first == _target;
}
};
struct HaveProperty {
std::string const _target;
bool operator()(Value const& v) const {
return v.second.get_optional<std::string>(_target).is_initialized();
}
};
And one transformation to project to our wrapper objects:
template <typename T>
struct As {
T operator()(Value const& v) const {
return T { v.second };
}
};
}
Full Live Demo
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <boost/range/adaptors.hpp>
using boost::property_tree::ptree;
namespace PtreeTools {
namespace detail {
using boost::adaptors::filtered;
using boost::adaptors::transformed;
using Value = ptree::value_type;
struct KeyName {
std::string const _target;
bool operator()(Value const& v) const {
return v.first == _target;
}
};
struct HaveProperty {
std::string const _target;
bool operator()(Value const& v) const {
return v.second.get_optional<std::string>(_target).is_initialized();
}
};
template <typename T>
struct As {
T operator()(Value const& v) const {
return T { v.second };
}
};
}
auto named(std::string const& name) {
return detail::filtered(detail::KeyName{name});
}
auto having(std::string const& name) {
return detail::filtered(detail::HaveProperty{name});
}
template <typename T>
auto as() {
return detail::transformed(detail::As<T>{});
}
}
struct Config {
ptree data_;
using Id = uint16_t;
struct Book {
ptree const& data_;
Id id() const { return data_.get<Id>("B_ID"); }
std::string title() const { return data_.get<std::string>("Title"); }
};
struct Library {
ptree const& data_;
Id id() const { return data_.get<Id>("L_ID"); }
auto books() const {
using namespace PtreeTools;
return data_ | named("Book") | having("B_ID") | as<Book>();
}
Book book(Id id) const {
for (const auto& book : books())
if (book.id() == id) return book;
throw std::out_of_range("book");
}
};
auto libraries() const {
using namespace PtreeTools;
return data_ | named("Library") | having("L_ID") | as<Library>();
}
Library library(Id id) const {
for (const auto& lib : libraries())
if (lib.id() == id) return lib;
throw std::out_of_range("library");
}
};
#include <iostream>
int main() {
Config cfg;
{
std::ifstream ifs("input.txt");
read_json(ifs, cfg.data_);
}
std::cout << "Book title: " << cfg.library(2).book(1).title() << "\n";
}
Prints:
Book title: Velocity
回答2:
@Sehe fixed your JSON to be syntactically correct, but I think it would make sense to go a bit further than that. Given the data you're representing, it would make a great deal more sense to have an array of libraries, each of which contains an array of books, giving data something like this:
{
"Libraries": [
{
"L_ID": 1,
"Books": [
{
"B_ID": 1,
"Title": "Moby Dick"
},
{
"B_ID": 2,
"Title": "Jurassic Park"
}
]
},
{
"L_ID": 2,
"Books": [
{
"B_ID": 1,
"Title": "Velocity"
},
{
"B_ID": 2,
"Title": "Creeper"
}
]
}
]
}
Then, if at all possible, I'd choose a library that's actually suited to the job at hand. Boost property tree isn't really intended as a general-purpose JSON library. You can push it into that role if you really insist, but at least for what you've outlined in the question, it puts you through quite a bit of extra work to get what you want.
Personally, I'd probably use nlohmann's JSON library instead. Using it, we can proceed a little more directly to a solution. Basically, once it's parsed a JSON file, we can treat the result a great deal like we would normal collections from the standard library--we can use all the normal algorithms, range-based for
loops, and so on. So, we can load a JSON file something like:
using json=nlohmann::json;
std::ifstream in("libraries.json");
json lib_data;
in >> lib_data;
Then we can look through the libraries for a particular ID number with code something like this:
for (auto const &lib : libs["Libraries"])
if (lib["L_ID"] == lib_num)
// we've found the library we want
Looking for a particular book is nearly identical:
for (auto const &book : lib["Books"])
if (book["B_ID"] == book_num)
// we've found the book we want
From there, printing out the title looks something like: std::cout << book["Title"];
Putting those together, we could end up with code something like this:
#include "json.hpp"
#include <fstream>
#include <iostream>
using json = nlohmann::json;
std::string find_title(json lib_data, int lib_num, int book_num) {
for (auto const &lib : lib_data["Libraries"])
if (lib["L_ID"] == lib_num)
for (auto const &book : lib["Books"])
if (book["B_ID"] == book_num)
return book["Title"];
return "";
}
int main() {
std::ifstream in("libraries.json");
json lib_data;
in >> lib_data;
std::cout << find_title(lib_data, 1, 2);
}
If you really want to convert each JSON object into a C++ object, you can do that fairly easily as well. It would look something like this:
namespace library_stuff {
struct Book {
int B_ID;
std::string title;
};
void from_json(json &j, Book &b) {
b.B_ID = j["B_ID"];
b.title = j["Title"];
}
}
So there are two points here: you must name the function from_json
, and it must be defined in the same namespace as the struct/class it's associated with. With this bit of scaffolding in place, we can convert from JSON to struct with a simple assignment, something like this:
book b = lib_data["Libraries"][0]["Books"][1];
I'd consider it highly questionable whether that's really useful in this particular case though.
来源:https://stackoverflow.com/questions/45198739/iterate-through-multilevel-boost-tree