Iterate through multilevel boost tree - boost

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

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

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

Related

vectorXd from_json()?

I'm trying to implement json serialization of a class using eigen::VectorXd and nlohmann-json library. It's not a problem to store the class as JSON string. How to parse VectorXd from JSON? Is there an other library more suitable for this task?
#include "json.hpp"
class TransformationStep {
public:
VectorXd support_vector;
int number;
TransformationStep(int number_param, VectorXd support_vectorParam) {
number = number_param;
support_vector = support_vectorParam;
}
~TransformationStep() {
}
//json serialization
void to_json(nlohmann::json &j);
void from_json(const nlohmann::json &j);
};
void TransformationStep::to_json(nlohmann::json &j) {
j["number"] = number;
j["support_vector"] = support_vector;
}
void Ftf::from_json(const nlohmann::json &j)
{
number = (j.at("number").get<int>());
//support_vector = j["support_vector"].get<VectorXd>()); //???
}
------ output calling to_json(nlohmann::json &j) ------
{
"number": 3,
"support_vector": [
-0.00036705693279489064,
0.020505439899631835,
0.3531380358938106,
0.0017673029092790872,
-0.9333248513057808,
0.04670404618976708,
-0.21905858722244081,
-1.011945322347849,
-0.09172040021815037,
0.008526811888809391,
0.05187648010664058
]
}
I came up with
void vector_from_json(VectorXd& vector, const nlohmann::json &j) {
vector.resize(j.size());
size_t element_index=0;
for (const auto& element : j) {
vector(element_index++) = (double) element;
}
}

Possible create an array of different structures in C++

I have some struct like this:
struct A { ... };
struct B { ... };
And I have a template like this:
template<typename struct_arg>
class X { ... }
Now I wanna create an array of arguments as struct like this:
args [2] { A, B };
for (args) {
X<args[i]> x;
}
Can I possible to create an array like this!?
Yes, you can do. std::variant is made for this usage and you can access such members of a variant with std::visit.
But if you do so, keep in mind, that each element of the array has an additional data member which has the type information and that each element has at minimum the size of the largest type you store. And also std::visit comes with a overhead, as a table for access the data member must be created. Typically done in compile time, but sometime g++ generates it in run time, which will decrease speed a lot!
struct A
{
void Do() { std::cout << "A" << std::endl; }
};
struct B
{
void Do() { std::cout << "B" << std::endl; }
};
int main()
{
std::array<std::variant< A,B >,2> arr{ A{}, B{}, B{}, A{} };
for ( auto& element: arr )
{
std::visit( []( auto& vari ) { vari.Do(); }, element );
}
}
Or if you like your encapsulation with an additional strucuture/class like given in your example:
struct A
{
void Do() { std::cout << "A" << std::endl; }
};
struct B
{
void Do() { std::cout << "B" << std::endl; }
};
template < typename struct_arg >
struct X: public struct_arg{};
int main()
{
std::array<std::variant< X<A>,X<B> >,2> arr{ X<A>{}, X<B>{} };
for ( auto& element: arr )
{
std::visit( []( auto& vari ) { vari.Do(); }, element );
}
}

c++ export an array of struct

Hello i'v the following function that exports a single instance of a struct :
struct UserIdentity
{
std::string name;
int id;
};
std::map<std::string, int> g_UserIdentities = { { "Bob", 100 }, { "Jone", 101 },
{ "Alice", 102 }, { "Doe", 103 } };
/*
* Will be used in a DLL that will export UserIdentity struct
* OUT _UserIdentity
*/
void Ui_export(UserIdentity *_UserIdentity)
{
for (auto& t : g_UserIdentities)
{
_UserIdentity->name = t.first;
_UserIdentity->id = t.second;
}
}
So please how could i export an array of _UserIdentity instead of a single instance in my function
void Ui_export(UserIdentity *_UserIdentity)
thank you .

How to pass a compile time array with var length to a constructor?

I have the following code:
struct MyArrayEntry
{
int type;
int id;
};
template<size_t arraySize>
struct MyArray
{
template<typename T, typename... Types>
MyArray(T t, Types... ts) : data{ { t, ts... } } {}
int dataSize = arraySize;
MyArrayEntry data[arraySize];
};
void Blah()
{
static MyArray<3> kTest
(
{ 1, 4 },
{ 2, 5 },
{ 3, 6 }
);
}
But this fails to build with:
error C2661: 'MyArray<3>::MyArray': no overloaded function takes 3
arguments
What am I doing wrong here?
With the imformation you provide, I would suggest using a std::initializer_list and an std::copy call:
template<size_t arraySize>
struct MyArray
{
const int dataSize = arraySize; // Could as well make it constant
MyArrayEntry data[arraySize];
MyArray(std::initializer_list<MyArrayEntry> elements)
{
std::copy(begin(elements), end(elements), std::begin(data));
}
};
Create as
MyArray<3> kTest({ { 1, 4 }, { 2, 5 }, { 3, 6 } });
Sure it's an extra pair of curly-brackets {}, but it will make your code simpler.

Extending std::vector<std::pair<...>> with own functionality

My C++11 is too weak to find a solution. I have lot of std::vector<std::pair<const char *, int>> variables in my project and therefore the code to check if an entry does exist repeats:
std::vector<std::pair<const char *, RunningProgramMode>> vProgramMode =
{
{ "server", RunningProgramModeServer },
{ "shell", RunningProgramModeShell },
{ "client", RunningProgramModeClient },
};
// the following code repeats for each variable
for ( auto const &it : vProgramMode )
{
if ( !strcmp(sParameter, it.first) )
{
programParameters->requestedProgramMode = it.second;
}
}
Of course I can write a function [which receives std::vector<std::pair<..>> as parameter] which iterates through the vector but I think it would be more elegant when I can extend the std::vector template with my find_member() function which checks with !strcmp(sParameter, it.first) if the vector has the requested entry and returns then .second value.
Something like this:
std::my_vector<std::pair<const char *, RunningProgramMode>> vProgramMode =
{
{ "server", RunningProgramModeServer },
{ "shell", RunningProgramModeShell },
{ "client", RunningProgramModeClient },
};
result = vProgramMode.find_member("shell");
For the moment there is no need to check if the value does exist. I want to keep the example simple and focus on the problem.
My solution:
template<typename T>
class MyVectorForPair
{
private:
std::vector<std::pair<const char *, T>> classObject;
public:
MyVectorForPair(std::vector<std::pair<const char *, T>> initVector)
{ classObject = initVector; }
auto find_member(const char * sMember, T defaultReturn) -> T;
};
template<typename T>
auto MyVectorForPair<T>::find_member(const char * sMember, T defaultReturn) -> T
{
for ( auto const &it : classObject )
{
if ( !strcmp(sMember, it.first) )
{
return it.second;
}
}
return defaultReturn;
}
I can use it then like this - now it is general:
MyVectorForPair<RunningProgramMode> vProgramMode
(
{
{ "server", RunningProgramModeServer },
{ "shell", RunningProgramModeShell },
{ "client", RunningProgramModeClient },
}
);
RunningProgramMode result;
result = vProgramMode.find_member(sParameter, RunningProgramModeNotSelected));

Resources