Read a sub json using boost property tree
The body is not a string.
So, getting the child object would be in order:
for (auto const& v : jsonBatchResponse.get_child("responses")) {
std::string strID = v.second.get<std::string>("id", "");
std::string strStatus = v.second.get<std::string>("status", "");
ptree const& body = v.second.get_child("body");
}
If you add some output to that loop with e.g.
std::cout << std::quoted(strID) << "\n";
std::cout << std::quoted(strStatus) << "\n";
write_json(std::cout, body);
It will print Live On Coliru
"1"
"200"
{
"createdDateTime": "2021-04-22T09:24:59.394Z",
"displayName": "Test1",
"visibility": "public",
"isMembershipLimitedToOwners": "false",
"discoverySettings": {
"showInTeamsSearchAndSuggestions": "true"
},
"memberSettings": {
"allowCreateUpdateChannels": "true",
"allowCreateUpdateRemoveConnectors": "true"
},
"guestSettings": {
"allowCreateUpdateChannels": "true",
"allowDeleteChannels": "false"
},
"messagingSettings": {
"allowUserEditMessages": "true",
"allowChannelMentions": "true"
},
"funSettings": {
"allowGiphy": "true",
"allowCustomMemes": "true"
}
}
BONUS: Using a proper JSON library instead
Boost Property Tree is NOT a JSON library, and therefore has a lot of limitations.
Instead I suggest using Boost JSON:
Live On Coliru
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#include <iostream>
namespace json = boost::json;
extern std::string sample;
int main() {
json::object jsonBatchResponse = json::parse(sample).as_object();
for (auto& v : jsonBatchResponse["responses"].as_array()) {
auto& res = v.as_object();
json::value id = res["id"], // string
status = res["status"], // integer
body = res["body"]; // object
std::cout << id << "\n";
std::cout << status << "\n";
std::cout << body << "\n";
}
}
std::string sample = R"(
{
"responses": [{
"id": "1",
"status": 200,
"headers": {
"OData-Version": "4.0",
"Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true"
},
"body": {
"createdDateTime": "2021-04-22T09:24:59.394Z",
"displayName": "Test1",
"visibility": "public",
"isMembershipLimitedToOwners": false,
"discoverySettings": {
"showInTeamsSearchAndSuggestions": true
},
"memberSettings": {
"allowCreateUpdateChannels": true,
"allowCreateUpdateRemoveConnectors": true
},
"guestSettings": {
"allowCreateUpdateChannels": true,
"allowDeleteChannels": false
},
"messagingSettings": {
"allowUserEditMessages": true,
"allowChannelMentions": true
},
"funSettings": {
"allowGiphy": true,
"allowCustomMemes": true
}
}
}]
}
)";
Prints
"1"
200
{"createdDateTime":"2021-04-22T09:24:59.394Z","displayName":"Test1","visibility":"public","isMembershipLimitedToOwners":false,"discoverySettings":{"showInTeamsSearchAndSuggestions":true},"memberSettings":{"allowCreateUpdateChannels":true,"allowCreateUpdateRemoveConnectors":true},"guestSettings":{"allowCreateUpdateChannels":true,"allowDeleteChannels":false},"messagingSettings":{"allowUserEditMessages":true,"allowChannelMentions":true},"funSettings":{"allowGiphy":true,"allowCustomMemes":true}}
boost: parse json file and get a child
Use a JSON library. Property Tree is not a JSON library. Using Boost JSON and JSON Pointer:
Live On Coliru
#include <boost/json.hpp>
#include <boost/json/src.hpp> // for header-only
#include <iostream>
#include <string_view>
auto getField(std::string_view json, std::string_view field) {
return boost::json::parse(json).at_pointer(field);
}
int main() {
auto output = R"({
"data": {
"device_id": 67,
"place_open": {
"monday": ["10:15", "19:30"]
}
}
})";
for (auto pointer : {
"/data",
"/data/device_id",
"/data/place_open/monday",
"/data/place_open/monday/0",
"/data/place_open/monday/1",
})
std::cout << pointer << " -> " << getField(output, pointer) << std::endl;
}
Prints
/data -> {"device_id":67,"place_open":{"monday":["10:15","19:30"]}}
/data/device_id -> 67
/data/place_open/monday -> ["10:15","19:30"]
/data/place_open/monday/0 -> "10:15"
/data/place_open/monday/1 -> "19:30"
Read & Parse a JSON file c++ BOOST
I'm not completely sure what the problem is. It seems to work (once you make the JSON valid):
UPDATE: Boost JSON
Boost 1.75.0 introduced Boost JSON, far superior way to actually deal with Json: Live On Wandbox
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#include <iostream>
#include <iterator>
#include <fstream>
namespace json = boost::json;
struct Rec {
int64_t number;
std::string string;
friend Rec tag_invoke(json::value_to_tag<Rec>, json::value const& v) {
auto& o = v.as_object();
return {
o.at("number").as_int64(),
value_to<std::string>(o.at("string")),
};
}
friend void tag_invoke(json::value_from_tag, json::value& v, Rec const& rec)
{
v = json::object{
{"number", rec.number},
{"string", rec.string},
};
}
};
int main() {
std::ifstream ifs("input.txt");
std::string input(std::istreambuf_iterator<char>(ifs), {});
using Recs = std::vector<Rec>;
Recs recs = value_to<std::vector<Rec>>(json::parse(input));
for (auto& [n, s] : recs) {
std::cout << "Rec { " << n << ", " << std::quoted(s) << " }\n";
// some frivolous changes:
n *= 2;
reverse(begin(s), end(s));
}
std::cout << "Modified json: " << json::value_from(recs) << "\n";
}Printing
Rec { 1234, "hello world" }
Rec { 5678, "foo bar" }
Modified json: [{"number":2468,"string":"dlrow olleh"},{"number":11356,"string":"rab oof"}]
Live On Coliru
#include "boost/property_tree/ptree.hpp"
#include "boost/property_tree/json_parser.hpp"
int main() {
using boost::property_tree::ptree;
std::ifstream jsonFile("input.txt");
ptree pt;
read_json(jsonFile, pt);
for (auto & array_element: pt) {
for (auto & property: array_element.second) {
std::cout << property.first << " = " << property.second.get_value < std::string > () << "\n";
}
}
}
With input.txt
containing:
[{"number": 1234, "string": "hello world"},{"number": 5678, "string": "foo bar"}]
Prints
number = 1234
string = hello world
number = 5678
string = foo bar
Reading JSON with Boost property_tree
To get nested elements you can use the path syntax where each path component is separated by "."
. Things are a little bit more complicated here because the child node b
is an array. So you can't do without a loop.
const pt::ptree& b = jsontree.get_child("b");
for( const auto& kv : b ){
cout << "b_b_a = " << kv.second.get<string>("b_b.b_b_a") << "\n";
}
Live demo at Coliru.
I've also added code to print the whole tree recursively so you can see how the JSON gets translated to the ptree. Arrays elements are stored as key/value pairs where the key is an empty string.
How can I read an array of object from a JSON file using boost-property-tree in C++
In property_tree
arrays JSON are mapped into nodes (called just ptree
).
What is node/ptree ?
node/ptree {
data // has data
list < pair<key,node> > children // has children
}
In your input object you have property users with value as the array with 3 elements. These elements are mapped into three nodes with empty string as key.
So we have:
"users" node:
children {"",nodeA}, {"",nodeB}, {"",nodeC}
nodeA,B,C
represents elements of arrays. Each element of array is object with 2 properties. Objects like arrays are also mapped into nodes.
So nodeA
looks like:
nodeA:
children {"firstName",nodeD},{"lastName",nodeE} \\ as children we have 2 properties of object
and finally, nodeD
is
nodeD:
data = John
children emptyList
To get users property call get_child()
.
To iterate over all children use begin\end
methods on ptree
returned by get
. begin\end
returns iterator to pair with first
as key, and second
as nested ptree instance.
The below code iterates over elements in array:
boost::property_tree::ptree pt;
boost::property_tree::read_json("in.json",pt);
auto it = pt.get_child("users");
for (auto it2 = it.begin(); it2 != it.end(); ++it2)
{
for (auto it3 = it2->second.begin(); it3 != it2->second.end(); ++it3)
{
std::cout << it3->first; // [1]
std::cout << " : " << it3->second.data() << std::endl;
}
// [2]
std::cout << std::endl;
}
and prints:
firstName : John
lastName : Black
firstName : Kate
lastName : Red
firstName : Robin
lastName : White
in [1] line you should store firstName
/lastName
and in [2] line you could create new User
instance and push into vector.
Reading JSON file with C++ and BOOST
Because the data structure in the other answer was deemed "very complex" and the target data structure was suggested to be:
struct Data {
struct Folder { int id, parent_id; std::string path; };
struct File { int id, parent_id; std::string path, name, md5_hash; };
using Folders = std::vector<Folder>;
using Files = std::vector<File>;
Folders folders;
Files files;
};
I ended up writing a transformation from generic "JSON" to that data structure (see the other answer: Reading JSON file with C++ and BOOST).
However, perhaps the OP will be more pleased if we "skip the middle man" and parse the JSON specifically into the shown Data
structure. This "simplifies" the grammar making it specific for this type of document only:
start = '{' >>
(folders_ >> commasep) ^
(files_ >> commasep)
>> '}';
folders_ = prop_key(+"folders") >> '[' >> -(folder_ % ',') >> ']';
files_ = prop_key(+"files") >> '[' >> -(file_ % ',') >> ']';
folder_ = '{' >> (
(prop_key(+"id") >> int_ >> commasep) ^
(prop_key(+"parent_id") >> int_ >> commasep) ^
(prop_key(+"path") >> text_ >> commasep)
) >> '}';
file_ = '{' >> (
(prop_key(+"id") >> int_ >> commasep) ^
(prop_key(+"parent_id") >> int_ >> commasep) ^
(prop_key(+"path") >> text_ >> commasep) ^
(prop_key(+"name") >> text_ >> commasep) ^
(prop_key(+"hash") >> text_ >> commasep)
) >> '}';
prop_key = lexeme ['"' >> lazy(_r1) >> '"'] >> ':';
commasep = &char_('}') | ',';
This grammar allows
- insignificant whitespace,
- re-ordering of properties within objects
- and omitted object properties
Benefits:
- early checking of property value types
- lower compile times
- less code indeed: 37 fewer LoC (not counting the sample JSON lines that's ~22%)
That last benefit has a flip side: if ever you want to read slightly different JSON, now you need to muck with the grammar instead of just writing a different extraction/transform. At 37 lines of code, my preference is with the other answer but I'll leave it to you to decide.
Here's the same demo program using this grammar directly:
Live On Coliru
//#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
static std::string const sample = R"(
{
"folders" :
[{
"id" : 109,
"parent_id" : 110,
"path" : "\/1\/105\/110\/"
},
{
"id" : 110,
"parent_id" : 105,
"path" : "\/1\/105\/"
}
],
"files" :
[{
"id" : 26,
"parent_id" : 105,
"name" : "picture.png",
"hash" : "md5_hash",
"path" : "\/1\/105\/"
},
{
"id" : 25,
"parent_id" : 110,
"name" : "another_picture.jpg",
"hash" : "md5_hash",
"path" : "\/1\/105\/110\/"
}
]
})";
struct Data {
struct Folder { int id, parent_id; std::string path; };
struct File { int id, parent_id; std::string path, name, md5_hash; };
using Folders = std::vector<Folder>;
using Files = std::vector<File>;
Folders folders;
Files files;
};
BOOST_FUSION_ADAPT_STRUCT(Data::Folder, (int,id)(int,parent_id)(std::string,path))
BOOST_FUSION_ADAPT_STRUCT(Data::File, (int,id)(int,parent_id)(std::string,path)(std::string,name)(std::string,md5_hash))
BOOST_FUSION_ADAPT_STRUCT(Data, (Data::Folders,folders)(Data::Files,files))
namespace folder_info { // adhoc JSON parser
template <typename It, typename Skipper = qi::space_type>
struct grammar : qi::grammar<It, Data(), Skipper>
{
grammar() : grammar::base_type(start) {
using namespace qi;
start = '{' >>
(folders_ >> commasep) ^
(files_ >> commasep)
>> '}';
folders_ = prop_key(+"folders") >> '[' >> -(folder_ % ',') >> ']';
files_ = prop_key(+"files") >> '[' >> -(file_ % ',') >> ']';
folder_ = '{' >> (
(prop_key(+"id") >> int_ >> commasep) ^
(prop_key(+"parent_id") >> int_ >> commasep) ^
(prop_key(+"path") >> text_ >> commasep)
) >> '}';
file_ = '{' >> (
(prop_key(+"id") >> int_ >> commasep) ^
(prop_key(+"parent_id") >> int_ >> commasep) ^
(prop_key(+"path") >> text_ >> commasep) ^
(prop_key(+"name") >> text_ >> commasep) ^
(prop_key(+"hash") >> text_ >> commasep)
) >> '}';
prop_key = lexeme ['"' >> lazy(_r1) >> '"'] >> ':';
commasep = &char_('}') | ',';
////////////////////////////////////////
// Bonus: properly decoding the string:
text_ = '"' >> *ch_ >> '"';
ch_ = +(
~char_("\"\\")) [ _val += _1 ] |
qi::lit("\x5C") >> ( // \ (reverse solidus)
qi::lit("\x22") [ _val += '"' ] | // " quotation mark U+0022
qi::lit("\x5C") [ _val += '\\' ] | // \ reverse solidus U+005C
qi::lit("\x2F") [ _val += '/' ] | // / solidus U+002F
qi::lit("\x62") [ _val += '\b' ] | // b backspace U+0008
qi::lit("\x66") [ _val += '\f' ] | // f form feed U+000C
qi::lit("\x6E") [ _val += '\n' ] | // n line feed U+000A
qi::lit("\x72") [ _val += '\r' ] | // r carriage return U+000D
qi::lit("\x74") [ _val += '\t' ] | // t tab U+0009
qi::lit("\x75") // uXXXX U+XXXX
>> _4HEXDIG [ append_utf8(qi::_val, qi::_1) ]
);
BOOST_SPIRIT_DEBUG_NODES((files_)(folders_)(file_)(folder_)(start)(text_))
}
private:
qi::rule<It, Data(), Skipper> start;
qi::rule<It, Data::Files(), Skipper> files_;
qi::rule<It, Data::Folders(), Skipper> folders_;
qi::rule<It, Data::File(), Skipper> file_;
qi::rule<It, Data::Folder(), Skipper> folder_;
qi::rule<It, void(const char*), Skipper> prop_key;
qi::rule<It, std::string()> text_, ch_;
qi::rule<It> commasep;
struct append_utf8_f {
template <typename...> struct result { typedef void type; };
template <typename String, typename Codepoint>
void operator()(String& to, Codepoint codepoint) const {
auto out = std::back_inserter(to);
boost::utf8_output_iterator<decltype(out)> convert(out);
*convert++ = codepoint;
}
};
boost::phoenix::function<append_utf8_f> append_utf8;
qi::uint_parser<uint32_t, 16, 4, 4> _4HEXDIG;
};
template <typename Range, typename It = typename boost::range_iterator<Range const>::type>
Data parse(Range const& input) {
grammar<It> g;
It first(boost::begin(input)), last(boost::end(input));
Data parsed;
bool ok = qi::phrase_parse(first, last, g, qi::space, parsed);
if (ok && (first == last))
return parsed;
throw std::runtime_error("Remaining unparsed: '" + std::string(first, last) + "'");
}
}
int main()
{
auto parsed = folder_info::parse(sample);
for (auto& e : parsed.folders)
std::cout << "folder:\t" << e.id << "\t" << e.path << "\n";
for (auto& e : parsed.files)
std::cout << "file:\t" << e.id << "\t" << e.path << "\t" << e.name << "\n";
}
Output:
folder: 109 /1/105/110/
folder: 110 /1/105/
file: 26 /1/105/ picture.png
file: 25 /1/105/110/ another_picture.jpg
Related Topics
Using Sfinae for Template Class Specialisation
Forcing MAChine to Use Dedicated Graphics Card
How to Compile Openssl for X64
Member Function with Static Linkage
C++ Handling of Excess Precision
How Does the C++ Compiler Know Which Implementation of a Virtual Function to Call
How to Enumerate/List All Installed Applications in Windows Xp
What Use Are Const Pointers (As Opposed to Pointers to Const Objects)
How to Install C++ Plugin to Eclipse
"To_String" Isn't a Member of "Std"
Why Doesn't C++ Have &&= or ||= for Booleans
Opencv: How to Visualize a Depth Image
Why Can't I Inherit from Int in C++
Why Do C++ Streams Use Char Instead of Unsigned Char