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.
Related
I am creating a desktop app using QT C++ that take a text file and convert it to JSON File like this example:
{
"102": {
"NEUTRAL": {
"blend": "100"
},
"AE": {
"blend": "100"
}
},
"105": {
"AE": {
"blend": "100"
},
"NEUTRAL": {
"blend": "100"
}
}
}
This is the code I am using:
for (int i = 0; i < output_list1.size(); i++) {
if (output_list1[i] == "-") {
c_frame++;
continue;
}
if (output_list1[i] != "NEUTRAL") {
QJsonObject neutralBlendObject;
neutralBlendObject.insert("blend", "100");
QJsonObject phonemeObject;
phonemeObject.insert("NEUTRAL", neutralBlendObject);
QJsonObject keyBlendObject;
keyBlendObject.insert("blend", output_list1[i].split(' ')[1]);
phonemeObject.insert(output_list1[i].split(' ')[0], keyBlendObject);
mainObject.insert(QString::number(c_frame), phonemeObject);
}
c_frame++;
}
jsonDoc.setObject(mainObject);
file.write(jsonDoc.toJson());
file.close();
As you can see, I am inserting the NEUTRAL object first but I am getting data not in the correct order, somtimes NEUTRAL is the the first following with the next object and somtimes not.
How can I correct this issue?
In JSON there are two structures you can use for saving data:
1.) JSON Objects: A collection of key/value pairs. This collection is NOT ordered. In various languages its realized via associative data structures like dictionaries, hash tables etc. Internally qt saves JSON objects via QHash<QString, QVariant> containers.
2.) JSON Arrays: An ordered list of values (which can be JSON Objects). In programming languages this is realized as an array/vector/whatever. Qt uses a QVariantList (QList<QVariant) internally.
I did resolve it using rapidjson library instead of QJsonObject
Based on this example
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include "rapidjson/filewritestream.h"
#include <string>
#include "RapidJson/prettywriter.h"
using namespace rapidjson;
using namespace std;
FILE* fp = fopen("output.json", "wb"); // non-Windows use "w"
char writeBuffer[65536];
FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer));
Document d;
d.SetObject();
rapidjson::Document::AllocatorType& allocator = d.GetAllocator();
size_t sz = allocator.Size();
d.AddMember("version", 1, allocator);
d.AddMember("testId", 2, allocator);
d.AddMember("group", 3, allocator);
d.AddMember("order", 4, allocator);
Value tests(kArrayType);
Value obj(kObjectType);
Value val(kObjectType);
string description = "a description";
//const char *description = "a description";
//val.SetString()
val.SetString(description.c_str(), static_cast<SizeType>(description.length()), allocator);
obj.AddMember("description", val, allocator);
string help = "some help";
val.SetString(help.c_str(), static_cast<SizeType>(help.length()), allocator);
obj.AddMember("help", val, allocator);
string workgroup = "a workgroup";
val.SetString(workgroup.c_str(), static_cast<SizeType>(workgroup.length()), allocator);
obj.AddMember("workgroup", val, allocator);
val.SetBool(true);
obj.AddMember("online", val, allocator);
tests.PushBack(obj, allocator);
d.AddMember("tests", tests, allocator);
// Convert JSON document to string
PrettyWriter<FileWriteStream> writer(os);
char indent = ' '; // single space x 4
writer.SetIndent(indent, 4);
d.Accept(writer);
Thank you all for your time
I have a json string that looks like this:
[
"some text",
648547,
94.0,
111.0267520223,
10
]
so I need to assign a variable to each value like:
std::string value1 = "some text";
int value2 = 648547;
float value3 = 94.0;
float value4 = 111.0267520223;
int value5 = 10;
to read JSON, with Boost, I was doing something like this
std::stringstream jsonResponse;
boost::property_tree::ptree pt;
jsonResponse << "[\"some text\", 648547, 94.0, 111.0267520223, 10]";
std::istringstream is(jsonResponse);
boost::property_tree::read_json(is, pt);
But I don't know how to read array values from a property tree.
Does anyone have an idea how to do it?
thanks in advance!
Here my solution to iterate over no naming array:
boost::property_tree::basic_ptree<std::string,std::string>::const_iterator iter = pt.begin(),iterEnd = pt.end();
for(;iter != iterEnd;++iter)
{
//->first; // Key. Array elements have no names
//->second; // The object at each step
std::cout << "=> " << iter->second.get_value<std::string>() << std::endl;
}
You'll need to name the array so that it can be referenced:
{
"blah": [
"some text",
648547,
94.0,
111.0267520223,
10
]
}
This will validate on jsonlint.com, but it's still not simple to read using a property tree.
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/exceptions.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/foreach.hpp>
typedef boost::property_tree::iptree ptree_t;
typedef ptree_t::value_type ptree_value_t;
typedef boost::optional<ptree_t &> optional_ptree_t;
void parseMyJson()
{
optional_ptree_t ptBlah = pt.get_child_optional("blah");
if (ptBlah)
{
BOOST_FOREACH (property_tree_t::value_type & field, pt.get_child("blah"))
{
}
}
}
With this kind of code you can iterate the fields in blah, but since they're different types, its not straightforward to parse.
I would suggest that you consider naming the fields so they can be directly referenced.
e.g.
field.second.get<string>("fieldname", "");
Please remember to wrap this code in a trycatch block, since boost property trees throw exceptions at the first sign of a problem (e.g. parse failure, or field not found etc.)
You might like to consider a more user friendly json library (https://github.com/nlohmann/json).
I know how to parse "normal" looking JSON data in C++. Usually, I do this, using boost::property_tree and read_json method. It may look like so:
BOOST_FOREAH(ptree::value_type &v, pt.get_child("rows"){
vec.push_back(v.second.get<std::string>("key"));
}
and the code above corresponds to this JSON file:
{
"rows":[{
"key":"1"
},{
"key":"2"
}]
}
However, the Neo4j result-set that I get, looks like:
{
"columns":{...},
"data":[[["object 1"]], [["object 2"]], [["object 3"]]]
}
I'm interested and want to parse "data" node. I tried to do it like so:
BOOST_FOREAH(ptree::value_type &v, pt.get_child("data"){
vec.push_back(v.second.data());
}
but this does not work. I do not get an error, but my vector vec remains empty, or to be more precise it is populated with empty values. So, that when I iterate through this vec I see a number of elements, but they do not have any value. Whereas, I want to have values "object 1", "object 2", "object 3".
The solution looks like this:
using boost::property::ptree;
ptree pt;
//... populate ptree pt with data from some source
BOOST_FOREACH(ptree::value_type &v, pt.get_child('data')){
ptree subtree1 = v.second;
BOOST_FOREACH(ptree::value_type &vs, subtree1){
ptree subtree2 = vs.second;
BOOST_FOREACH(ptree::value_type &vs2, subtree2){
do_something(vs2.second.data());
}
}
}
This code makes it possible to parse such JSON structure:
{
"data":[[["object 1"]], [["object 2"]], [["object 3"]]]
}
So, contrary to what some people are saying, actually, there is no need to use other third-party libraries. Use just boost and you are done.
This is an example of how I do it. You have to know the JSON structure ahead of time.
#include <boost/lexical_cast.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
boost::property_tree::ptree pt, sub_pt;
std::string json_str, key, sub_key;
std::stringstream ss;
int value = 0, bus_num = 0;
json_str = "{\"arduino_1\": {\"bus_1\": 17425,\"bus_2\": 1025,\"bus_3\": 0,\"bus_4\": 0,\"bus_5\": 0,\"bus_6\": 0,\"bus_7\": 0,\"bus_8\": 0}}";
ss << json_str; // put string into stringstream
boost::property_tree::read_json(ss, pt); // put stringstream into property tree
for (boost::property_tree::ptree::iterator iter = pt.begin(); iter != pt.end(); iter++)
{
// get data
key = boost::lexical_cast <std::string>(iter->first.data());
sub_pt = iter->second;
// iterate over subtree
for (boost::property_tree::ptree::iterator sub_iter = sub_pt.begin(); sub_iter != sub_pt.end(); sub_iter++)
{
// get data
sub_key = boost::lexical_cast <std::string>(sub_iter->first.data());
value = boost::lexical_cast <int>(sub_iter->second.data());
}
}
Consider the following example for which my source is
Json::Value root;
root["id"]=0;
Json::Value text;
text["first"]="i";
text["second"]="love";
text["third"]="you";
root["text"]=text;
root["type"]="test";
root["begin"]=1;
root["end"]=1;
Json::StyledWriter writer;
string strJson=writer.write(root);
cout<<"JSON WriteTest" << endl << strJson <<endl;
I thought I'd write the json fields in the order of the lines. Instead the result is:
JSON WriteTest
{
"begin" : 1,
"end" : 1,
"id" : 0,
"text" : {
"first" : "i",
"second" : "love",
"third" : "you"
},
"type" : "test"
}
I want json format is
JSON WriteTest
{
"id" : 0,
"text" : {
"first" : "i",
"second" : "love",
"third" : "you"
},
"type" : "test"
"begin" : 1,
"end" : 1,
}
How can I write a Json order?
No, I don't think you can. JsonCpp keeps its values in a std::map<CZString, Value>, which is always sorted by the CZString comparison. So it doesn't know the original order you added items.
This is my workaround to a get an ordered json output from jsoncpp
Json::Value root;
root["*1*id"]=0;
Json::Value text;
text["*1*first"]="i";
text["*2*second"]="love";
text["*3*third"]="you";
root["*2*text"]=text;
root["*3*type"]="test";
root["*4*begin"]=1;
root["*5*end"]=1;
Json::StyledWriter writer;
string resultString=writer.write(root);
resultString=ReplaceAll(resultString,"*1*", "");
resultString=ReplaceAll(resultString,"*2*", "");
resultString=ReplaceAll(resultString,"*3*", "");
resultString=ReplaceAll(resultString,"*4*", "");
resultString=ReplaceAll(resultString,"*5*", "");
cout<<"JSON WriteTest" << endl << resultString <<endl;
with RepleceAll function defined as this
std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) {
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
}
return str;
}
I have a way can solve your problem. Would you like to try? My solution is that you use boost/property_tree/json_parser.hpp, the output is what format you want! About There is my code:
#include <boost/property_tree/json_parser.hpp>
#include <sstream>
#include <iostream>
using namespace std;
int main()
{
boost::property_tree::ptree parser, child;
parser.put("id", 0);
child.put("first", "i");
child.put("second", "love");
child.put("third", "you");
parser.put_child("text", child);
parser.put("type", "test");
parser.put("begin", 1);
parser.put("end", 1);
stringstream ss;
boost::property_tree::json_parser::write_json(ss, parser);
cout << ss.str() << endl;
return 0;
}
Before run the codes, you should install boost 1.57. The codes run well in gcc 4.7, boost 1.57.The output is { "id" : 0, "text" : { "first" : "i", "second" : "love", "third" : "you" }, "type" : "test" "begin" : 1, "end" : 1, }. About boost::property_tree::ptree, you can click here. It used list<pair<key, ptree>> for saving data. So it saved unordered data, unless you called list.sort(). I hope this can help you.
As mentioned by The Dark, JsonCpp keeps its values in a std::map<CZString, Value>, which is always sorted by the CZString comparison, without keeping track neither of the original order in which you added the items nor the desired order in the output.
But you can use this "hidden feature" in your benefit. I mean, you just need that the keys in the desired order follow the "natural" order of CZString. I have a method in my JSONCPP wrapper classes that do this. The quick'n'dirty code, converted to simple function, would be something like this:
std::string sortedStr(Json::Value & value, std::vector<std::string> sortKeys)
{
Json::Value sortedValue; // The JSON object to store the new (sorted) hash
char newKey[60]; // I use C expressions, modify to C++ if you like
// Build new sortedValue
int i = 0;
for (auto & key : sortKeys) {
sprintf(newKey, "SORTEDKEY:%03d-%s", i++, key.c_str());
sortedValue[newKey] = value[key];
}
// Write to string, should be sorted on primary keys
Json::StyledWriter w;
std::string result = w.write(sortedValue);
// Remove aux. tags from primary keys
std::size_t pos = 0;
while ((pos = result.find("SORTEDKEY:", pos)) != std::string::npos) {
result.erase(pos, 14);
}
return result;
}
To use it, just call:
std::string sortedObjStr = sortedValue(myValue, {"first", "second", "third", "fourth"});
Note that:
I use this for relatively small objects (configuration data).
I use the "tag" SORTEDKEY, since this is not going to appear anywhere in my data. Modify according to your needs.
I do not check that the keys used do exist. You can add this check.
You can use this also to generate a restricted, ordered subset of your original object.
The key-value pairs in an object will always be sorted. Json arrays are not sorted, they consists of a series of values without keys.
Objects, as named collections (arrays) of key-value pairs within brackets, in an array, will retain their positions, e.g.
{
"adressen" : [
{
"start" : {
"ID" : 1,
"key" : "2352KJ25",
"lat" : 52.157225922529967,
"lon" : 4.5298663828345527
}
},
{
"eind" : {
"ID" : 2,
"key" : "2352KJ25",
"lat" : 52.157225922529967,
"lon" : 4.5298663828345527
}
}
}
ID, key, lat, lon are sorted, but start and eind are in their original positions.
So, at least your first, second, third could have been
Json::Value text(Json::arrayValue);
text.append("I");
text.append("love");
text.append("you");
No need for the tags first, second and third!
Maybe this helps you to find a workaround.
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());