Create JSON array of strings with jsoncpp - c++

I need to update an index (in JSON format) when writing a new file to disk, and since the files are categorized, I'm using an object with this kind of structure:
{ "type_1" : [ "file_1", "file_2" ], "type_2" : [ "file_3", "file_4" ] }
I thought it was an easy task for jsoncpp, but I'm probably missing something.
My code (simplified) here:
std::ifstream idx_i(_index.c_str());
Json::Value root;
Json::Value elements;
if (!idx_i.good()) { // probably doesn't exist
root[type] = elements = Json::arrayValue;
} else {
Json::Reader reader;
reader.parse(idx_i, root, false);
elements = root[type];
if (elements.isNull()) {
root[type] = elements = Json::arrayValue;
}
idx_i.close();
}
elements.append(name.c_str()); // <--- HERE LIES THE PROBLEM!!!
std::ofstream idx_o(_index.c_str());
if (idx_o.good()) {
idx_o << root;
idx_o.close();
} else {
Log_ERR << "I/O error, can't write index " << _index << std::endl;
}
So, I'm opening the file, reading JSON data works, if I can't find any, I create a new array, the problem is: when I try to append a value to the array, it doesn't work, the array remains empty, and is written to file.
{ "type_1" : [], "type_2" : [] }
Tried to debug my code, and the jsoncpp calls, and everything seems to be ok, but the array is always empty.

The problem arises here:
elements = root[type];
because you are creating a copy of root[type] when calling this JsonCpp API:
Value &Value::operator[]( const std::string &key )
thus not modifying root document at all. Simplest way to avoid this problem is, in your case, to not use the elements variable:
root[type].append(name.c_str());

Related

rapidjson's assert IsObject() fails randomly while it shouldn't

we are facing an issue where the RAPIDJSON_ASSERT(IsObject()) called by MemberEnd() which is called by HasMember() fails. However, that rapidjson::Value is guaranteed to be an object by other logic.
Here is the code snippet:
const std::string str = "{\"outer_key\":{\"inner_key\":\"value\", \"foo\":\"bar\"}}";
rapidjson::Document doc;
if (doc.Parse(str.c_str()).HasParseError()) {
return -1;
}
rapidjson::Value::MemberIterator it = doc.FindMember("outer_key");
// make sure the member is of Object type
if (it == doc.MemberEnd() || !it->value.IsObject()) {
return -1;
}
rapidjson::Value* p_json;
p_json = &(it->value);
rapidjson::Document new_doc;
rapidjson::Document::AllocatorType& new_doc_allocator = new_doc.GetAllocator();
// if SOME_FLAG is set and "outer_key" exists in the input JSON string,
// use "outer_key"'s value so that `new_doc` will have other properties such as `foo` for free.
if (SOME_FLAG && p_json != NULL) {
new_doc.CopyFrom(*p_json, new_doc_allocator);
// The value of "inner_key" will be re-added later, so remove it for now.
// assert(IsObject()) fails here and core is dumped.
if (new_doc.HasMember("inner_key")) {
new_doc.RemoveMember("inner_key");
}
} else {
// Otherwise, start from an empty object.
new_doc.SetObject();
}
// Add "inner_key" for both cases above.
const std::string new_value_str = "new_value";
new_doc.AddMember("inner_key", rapidjson::StringRef(new_value_str.c_str()), new_doc_allocator);
We suspect that the new_doc.CopyFrom(*p_json, new_doc_allocator); line is the culprit, but are kind of new to rapidjson and not sure whether it is the correct way to use new_doc_allocator to copy *p_json to new_doc.
The weird part is that this issue only happened online for a small number of requests. We extracted the input JSON string, checked that it is valid, but couldn't reproduce the failure offline.
It would be good if someone could point out where we did wrong. Thanks in advance.

Is there an elegant way to cascade-merge two JSON trees using jsoncpp?

I am using jsoncpp to read settings from a JSON file.
I would like to have two cascading settings file, say MasterSettings.json and LocalSettings.json where LocalSettings is a subset of MasterSettings. I would like to load MasterSettings first and then LocalSettings. Where LocalSettings has a value that differs from MasterSettings, that value would overwrite the one from MasterSettings. Much like the cascade in CSS.
Is there any elegant way to do this with jsoncpp?
I'm going to assume your settings files are JSON objects.
As seen here, when JSONCpp parses a file, it clears the contents of the root node. This mean that trying to parse a new file on top of the old one won't preserve the old data. However, if you parse both files into separate Json::Value nodes, it's straight forward to recursively copy the values yourself by iterating over the keys in the second object using getMemberNames.
// Recursively copy the values of b into a. Both a and b must be objects.
void update(Json::Value& a, Json::Value& b) {
if (!a.isObject() || !b.isObject()) return;
for (const auto& key : b.getMemberNames()) {
if (a[key].isObject()) {
update(a[key], b[key]);
} else {
a[key] = b[key];
}
}
}
I know it has been a while. but...
In addition to the correct answer and the commentary, here is a code version for those who use a older g++ version:
void jsonMerge(Json::Value &a, Json::Value &b) {
if (!a.isObject() || !b.isObject()) return;
vector<string> member_name = b.getMemberNames();
string key = "";
for (unsigned i = 0, len = member_name.size(); i < len; i++) {
key = member_name[i];
if (!a[key].isNull() && a[key].type() == Json::objectValue && b[key].type() == Json::objectValue) {
jsonMerge(a[key], b[key]);
} else {
a[key] = b[key];
}
}
member_name.clear();
}

JSONCPP Writing to files

JSONCPP has a writer, but all it seems to do is get info from the parser and then output it into a string or a stream. How do I make it alter or create new objects, arrays, values, strings, et cetera and write them into the file?
#include<json/writer.h>
Code:
Json::Value event;
Json::Value vec(Json::arrayValue);
vec.append(Json::Value(1));
vec.append(Json::Value(2));
vec.append(Json::Value(3));
event["competitors"]["home"]["name"] = "Liverpool";
event["competitors"]["away"]["code"] = 89223;
event["competitors"]["away"]["name"] = "Aston Villa";
event["competitors"]["away"]["code"]=vec;
std::cout << event << std::endl;
Output:
{
"competitors" :
{
"away" :
{
"code" : [ 1, 2, 3 ],
"name" : "Aston Villa"
},
"home" :
{
"name" : "Liverpool"
}
}
}
#include <json.h>
#include <iostream>
#include <fstream>
void main()
{
std::ofstream file_id;
op_file_id.open("file.txt");
Json::Value value_obj;
//populate 'value_obj' with the objects, arrays etc.
Json::StyledWriter styledWriter;
file_id << styledWriter.write(value_obj);
file_id.close();
}
AFAICT, you create objects of type Json::Value, which caters for all the JSON data-types, and pass the result to a Json::Writer (one of its derived types, to be specific), or simply to a stream.
E.g.: to write an array of three integers to a file:
Json::Value vec(Json::arrayValue);
vec.append(Json::Value(1));
vec.append(Json::Value(2));
vec.append(Json::Value(3));
std::cout << vec;
Json::StyledWriter is deprecated, you can use Json::StreamWriterBuilder to write json into files.
Json::Value rootJsonValue;
rootJsonValue["foo"] = "bar";
Json::StreamWriterBuilder builder;
builder["commentStyle"] = "None";
builder["indentation"] = " ";
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
std::ofstream outputFileStream("/tmp/test.json");
writer -> write(rootJsonValue, &outputFileStream);
The json will be written into /tmp/test.json.
$ cat /tmp/test.json
{
"foo" : "bar"
}
First, you have to create the desired JSON::Value. You should look at all the constructors (first). To create the necessary hierarchies, see append and the operator[] overloads; there are overloads for both array indices and string keys for objects.
One way to write the JSON value back out is using StyledStreamWriter::write and ofstream.
See cegprakash's answer for how to write it.

RapidXML, reading and saving values

I've worked myself through the rapidXML sources and managed to read some values. Now I want to change them and save them to my XML file:
Parsing file and set a pointer
void SettingsHandler::getConfigFile() {
pcSourceConfig = parsing->readFileInChar(CONF);
cfg.parse<0>(pcSourceConfig);
}
Reading values from XML
void SettingsHandler::getDefinitions() {
SettingsHandler::getConfigFile();
stGeneral = cfg.first_node("settings")->value();
/* stGeneral = 60 */
}
Changing values and saving to file
void SettingsHandler::setDefinitions() {
SettingsHandler::getConfigFile();
stGeneral = "10";
cfg.first_node("settings")->value(stGeneral.c_str());
std::stringstream sStream;
sStream << *cfg.first_node();
std::ofstream ofFileToWrite;
ofFileToWrite.open(CONF, std::ios::trunc);
ofFileToWrite << "<?xml version=\"1.0\"?>\n" << sStream.str() << '\0';
ofFileToWrite.close();
}
Reading file into buffer
char* Parser::readFileInChar(const char* p_pccFile) {
char* cpBuffer;
size_t sSize;
std::ifstream ifFileToRead;
ifFileToRead.open(p_pccFile, std::ios::binary);
sSize = Parser::getFileLength(&ifFileToRead);
cpBuffer = new char[sSize];
ifFileToRead.read( cpBuffer, sSize);
ifFileToRead.close();
return cpBuffer;
}
However, it's not possible to save the new value. My code is just saving the original file with a value of "60" where it should be "10".
Rgds
Layne
I think this is a RapidXML Gotcha
Try adding the parse_no_data_nodes flag to cfg.parse<0>(pcSourceConfig)
You should definitely be testing that the output file opened correctly and that your write succeeded. At the simplest, you need something like:
if ( ! ofFileToWrite << "<?xml version=\"1.0\"?>\n"
<< sStream.str() << '\0' ) {
throw "write failed";
}
Note that you don't need the '\0' terminator, but it shouldn't do any harm.
Use the following method to add an attribute to a node. The method uses the allocation of memory for strings from rapidxml. So rapidxml takes care of the strings as long as the document is alive. See http://rapidxml.sourceforge.net/manual.html#namespacerapidxml_1modifying_dom_tree for further information.
void setStringAttribute(
xml_document<>& doc, xml_node<>* node,
const string& attributeName, const string& attributeValue)
{
// allocate memory assigned to document for attribute value
char* rapidAttributeValue = doc.allocate_string(attributeValue.c_str());
// search for the attribute at the given node
xml_attribute<>* attr = node->first_attribute(attributeName.c_str());
if (attr != 0) { // attribute already exists
// only change value of existing attribute
attr->value(rapidAttributeValue);
} else { // attribute does not exist
// allocate memory assigned to document for attribute name
char* rapidAttributeName = doc.allocate_string(attributeName.c_str());
// create new a new attribute with the given name and value
attr = doc.allocate_attribute(rapidAttributeName, rapidAttributeValue);
// append attribute to node
node->append_attribute(attr);
}
}

Creating JSON arrays in Boost using Property Trees

I'm trying to create a JSON array using boost property trees.
The documentation says: "JSON arrays are mapped to nodes. Each element is a child node with an empty name."
So I'd like to create a property tree with empty names, then call write_json(...) to get the array out. However, the documentation doesn't tell me how to create unnamed child nodes. I tried ptree.add_child("", value), but this yields:
Assertion `!p.empty() && "Empty path not allowed for put_child."' failed
The documentation doesn't seem to address this point, at least not in any way I can figure out. Can anyone help?
Simple Array:
#include <boost/property_tree/ptree.hpp>
using boost::property_tree::ptree;
ptree pt;
ptree children;
ptree child1, child2, child3;
child1.put("", 1);
child2.put("", 2);
child3.put("", 3);
children.push_back(std::make_pair("", child1));
children.push_back(std::make_pair("", child2));
children.push_back(std::make_pair("", child3));
pt.add_child("MyArray", children);
write_json("test1.json", pt);
results in:
{
"MyArray":
[
"1",
"2",
"3"
]
}
Array over Objects:
ptree pt;
ptree children;
ptree child1, child2, child3;
child1.put("childkeyA", 1);
child1.put("childkeyB", 2);
child2.put("childkeyA", 3);
child2.put("childkeyB", 4);
child3.put("childkeyA", 5);
child3.put("childkeyB", 6);
children.push_back(std::make_pair("", child1));
children.push_back(std::make_pair("", child2));
children.push_back(std::make_pair("", child3));
pt.put("testkey", "testvalue");
pt.add_child("MyArray", children);
write_json("test2.json", pt);
results in:
{
"testkey": "testvalue",
"MyArray":
[
{
"childkeyA": "1",
"childkeyB": "2"
},
{
"childkeyA": "3",
"childkeyB": "4"
},
{
"childkeyA": "5",
"childkeyB": "6"
}
]
}
What you need to do is this piece of fun. This is from memory, but something like this works for me.
boost::property_tree::ptree root;
boost::property_tree::ptree child1;
boost::property_tree::ptree child2;
// .. fill in children here with what you want
// ...
ptree.push_back( std::make_pair("", child1 ) );
ptree.push_back( std::make_pair("", child2 ) );
But watch out there's several bugs in the json parsing and writing. Several of which I've submitted bug reports for - with no response :(
EDIT: to address concern about it serializing incorrectly as {"":"","":""}
This only happens when the array is the root element. The boost ptree writer treats all root elements as objects - never arrays or values. This is caused by the following line in boost/propert_tree/detail/json_parser_writer.hpp
else if (indent > 0 && pt.count(Str()) == pt.size())
Getting rid of the "indent > 0 &&" will allow it to write arrays correctly.
If you don't like how much space is produced you can use the patch I've provided here
When starting to use Property Tree to represent a JSON structure I encountered similar problems which I did not resolve. Also note that from the documentation, the property tree does not fully support type information:
JSON values are mapped to nodes containing the value. However, all type information is lost; numbers, as well as the literals "null", "true" and "false" are simply mapped to their string form.
After learning this, I switched to the more complete JSON implementation JSON Spirit. This library uses Boost Spirit for the JSON grammar implementation and fully supports JSON including arrays.
I suggest you use an alternative C++ JSON implementation.
In my case I wanted to add an array to a more or less arbitrary location, so, like Michael's answer, create a child tree and populate it with array elements:
using boost::property_tree::ptree;
ptree targetTree;
ptree arrayChild;
ptree arrayElement;
//add array elements as desired, loop, whatever, for example
for(int i = 0; i < 3; i++)
{
arrayElement.put_value(i);
arrayChild.push_back(std::make_pair("",arrayElement))
}
When the child has been populated, use the put_child() or add_child() function to add the entire child tree to the target tree, like this...
targetTree.put_child(ptree::path_type("target.path.to.array"),arrayChild)
the put_child function takes a path and a tree for an argument and will "graft" arrayChild into targetTree
As of boost 1.60.0, problem persists.
Offering a Python 3 workaround (Gist), which can be syscalled just after boost::property_tree::write_json.
#!/usr/bin/env python3
def lex_leaf(lf: str):
if lf.isdecimal():
return int(lf)
elif lf in ['True', 'true']:
return True
elif lf in ['False', 'false']:
return False
else:
try:
return float(lf)
except ValueError:
return lf
def lex_tree(j):
tj = type(j)
if tj == dict:
for k, v in j.items():
j[k] = lex_tree(v)
elif tj == list:
j = [lex_tree(l) for l in j]
elif tj == str:
j = lex_leaf(j)
else:
j = lex_leaf(j)
return j
def lex_file(fn: str):
import json
with open(fn, "r") as fp:
ji = json.load(fp)
jo = lex_tree(ji)
with open(fn, 'w') as fp:
json.dump(jo, fp)
if __name__ == '__main__':
import sys
lex_file(sys.argv[1])
If you want JSON in C++, there's no need for Boost. With this library you can get JSON as a first class data type that behaves like an STL container.
// Create JSON on the fly.
json j2 = {
{"pi", 3.141},
{"happy", true},
{"name", "Niels"},
{"nothing", nullptr},
{"answer", {
{"everything", 42}
}},
{"list", {1, 0, 2}},
{"object", {
{"currency", "USD"},
{"value", 42.99}
}}
};
// Or treat is as an STL container; create an array using push_back
json j;
j.push_back("foo");
j.push_back(1);
j.push_back(true);
// also use emplace_back
j.emplace_back(1.78);
// iterate the array
for (json::iterator it = j.begin(); it != j.end(); ++it) {
std::cout << *it << '\n';
}
Confused with the official document and the above answers.
Below is what I understand.
Property Tree consists of nodes.
Each node is like below
struct ptree
{
map<key_name,value> data;
vector<pair<key_name,ptree>> children;
};
To put 'value' into data with 'put'
To put 'node' into children with 'push_back'\
// Write
bt::ptree root;
bt::ptree active;
bt::ptree requested;
bt::ptree n1, n2, n3;
n1.put("name", "Mark");
n1.put("age", 20);
n1.put("job", "aaa");
n2.put("name", "Rosie");
n2.put("age", "19");
n2.put("job", "bbb");
n3.put("name", "sunwoo");
n3.put("age", "10");
n3.put("job", "ccc");
active.push_back ({ "",l1 });
active.push_back ({ "",l2 });
requested.push_back({ "",l3 });
root.push_back ({"active", active});
root.push_back ({"requested", requested});
bt::write_json("E:\\1.json", root);
// READ
bt::ptree root2;
bt::ptree active2;
bt::ptree requested2;
bt::ptree r1, r2, r3;
bt::read_json("E:\\1.json", root2);
// loop children
for (auto& [k,n] : root.get_child("active"))
{
cout << n.get<string>("name", "unknown");
cout << n.get<int> ("age" , 11);
cout << n.get<string>("job" , "man");
cout << endl << flush;
}