Related
How to deserialize data values?
{"data":
[{
"Id": 3,
"Name" : "Wind",
"Type" : 2,
"Order" : 1,
"user" : "Mike"
}],
"free" : 0,
"line" : 10,
"count" : 1
}
Here's what I've tried so far:
class Class {
public:
virtual ~Class(void);
virtual void Serialize(Json::Value& root);
virtual void Deserialize(Json::Value& root);
int Id;
std::string free;
std::string line;
std::string count;
std::vector<std::string> ID;
};
void Class::Serialize(Json::Value& root) {
// ...
}
void Class::Deserialize(Json::Value& root) {
free = root.get("top", 0).asInt();
line = root.get("skip", 0).asInt();
count = root.get("count", 0).asInt();
Json::Value Data= root["data"];
Id = Data.get("Id", 0).asInt();
}
I cannot deserialize the Data element Id, etc. The elements free, line and count can be extracted.
I have tried to create a second class to separate them, but that would not work either.
The JSON element data is an array. You missed the index of its element that you want to access.
At the moment, it has only one element so the index would be 0 i.e. Data[0].
Example:
Id = Data[0].get("Id", 0).asInt();
^^^
I have a program that modifies a JSON document if necessary. The program has to add a child to another value whether or not it's an already an object. The program should behave like so:
If the object with key "x" does not exist, create object with key "x" and add value y as a child.
If the object with key "x" DOES exist, set value y as a child.
If the key "x" exists and is ANY OTHER type, delete it, create an object with the key "x" and then add value y as a child.
I see ways to test if property tree values exist or whether they are specified types, but none to test if it's an object or not an object.
Here's a simple program I made illustrating what I mean:
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <sstream>
#include <iostream>
const char *json = "{"
"\"object\" : { \"mighty\" : \"wind\" },"
"\"boolean\" : true"
"}";
void printTree( std::string name, boost::property_tree::ptree tree )
{
std::cout << "Pass '" << name << "'" << std::endl;
try
{
std::stringstream ss;
boost::property_tree::write_json( ss, tree );
std::cout << ss.str() << std::endl;
}
catch( std::exception &e )
{
std::cout << "Could not make create json: " << e.what() << std::endl;
}
}
int main( int argc, char *argv[] )
{
boost::property_tree::ptree tree;
// Load it
std::istringstream ss_json( json );
boost::property_tree::read_json( ss_json, tree );
// Add a value to an object that doesn't exist
tree.put( "none.value", "hello!" );
// Print to see
printTree( "Nonexistent value test", tree );
// Add a value to the object
tree.put( "object.value", "bello!" );
// Print this one
printTree( "Adding value test", tree );
// Convert boolean to an object and add a value
tree.put( "boolean.value", "mello!" );
// Print it
printTree( "Converting value test", tree );
}
The output will be:
Pass 'Nonexistent value test'
{
"object": {
"mighty": "wind"
},
"boolean": "true",
"none": {
"value": "hello!"
}
}
Pass 'Adding value test'
{
"object": {
"mighty": "wind",
"value": "bello!"
},
"boolean": "true",
"none": {
"value": "hello!"
}
}
Pass 'Converting value test'
Could not make create json: <unspecified file>: ptree contains data that cannot be represented in JSON format
You can see in the output, the last step fails to convert to JSON (doesn't throw when I try to set it).
How can I achieve scenario 3 in my list above?
If the key "x" exists and is ANY OTHER type, delete it, create an object with the key "x" and then add value y as a child. Also, they don't observe any of the JSON data types.
Your plan is pretty doomed. Property Tree is not a JSON library. Property Trees can have data and child nodes at the same node. E.g.
ptree p;
auto& x = p.put_child("x", {});
x.put_value("hello");
write_json(std::cout, p);
Prints
{
"x": "hello"
}
But adding
/*auto& a = */ p.put_child("x.a", {});
write_json(std::cout, p);
Fails with Live On Coliru
terminate called after throwing an instance of 'boost::wrapexcept<boost::property_tree::json_parser::json_parser_error>'
what(): <unspecified file>: ptree contains data that cannot be represented in JSON format
A workaround would be to remove any value prior to or when adding properties:
x.put_value("");
auto& a = p.put_child("x.a", {});
a.add("prop1", 123);
a.add("prop2", "one two three");
a.add("b.prop1", "nesting");
write_json(std::cout, p);
Would print Live On Coliru
Finer notes
It might seem more efficient to check the presence of a value before clearing it:
if (x.get_value_optional<std::string>()) {
x.put_value("");
}
But due the the stringly typed nature of Property Tree storage there's no difference as the condition will just always be true for std::string. (Similarly there's no way to retrieve a value by reference.)
Note ALSO that when setting the n.prop1 nested property, you MAY have to also check that b has no value if you don't control the source data, because otherwise it would fail again.
Assuming that your object graph structure is reasonably predictable (or even static), I'd suggest getting it over with ahead of time:
for (auto key : { "x", "x.a", "x.a.b" }) {
if (auto child = p.get_child_optional(key)) {
std::cout << "clearing " << key << std::endl;
child->put_value("");
}
}
Which can be generalized with a helper:
clear_values("x.a.b", p);
Which could be implemented as
void clear_values(ptree::path_type path, ptree& p) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (child) {
child->put_value("");
clear_values(path, *child);
}
}
Bonus
In fact with such a helper it might become opportune to also create the expected hierarchy on the fly:
void clear_values(ptree::path_type path, ptree& p, bool create = false) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (!child && create) {
child = p.put_child(head, {});
}
if (child) {
child->put_value("");
clear_values(path, *child, create);
}
}
Now it would even work well without any pre-existing data:
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
void clear_values(ptree::path_type path, ptree& p, bool create = false) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (!child && create) {
child = p.put_child(head, {});
}
if (child) {
child->put_value("");
clear_values(path, *child, create);
}
}
int main() {
ptree p;
clear_values("x.a.b", p, true);
auto& a = p.get_child("x.a");
a.add("prop1", 123);
a.add("prop2", "one two three");
a.add("b.prop1", "nesting");
write_json(std::cout, p);
}
Prints
{
"x": {
"a": {
"b": {
"prop1": "nesting"
},
"prop1": "123",
"prop2": "one two three"
}
}
}
I started out by using a std::string as my map key, as each item in my map can be uniquely identified by a string alone.
Then I realised that it would be a lot more useful to me to have the map ordered in a certain way, based on another parameter, so I added an int called priority to my key to help with ordering. The idea is that I iterate over the map and process the higher priority items first. I now have the following user-defined struct as my map key :
struct MyKey {
// key data
std::string addr;
int priority;
// constructor
MyKey(const std::string & s, const int p)
: addr(s), priority(p) {}
// overloaded operator
bool operator<(const MyKey &that) const {
// same key if addr is the same
if (that->addr == this.addr)
return false;
// not same key so look at priorities to determine order
if (that.priority < this->priority)
return true;
if (that.priority > this->priority)
return false;
// priorities are the same so use the string compare
return (that.addr > this->addr);
}
};
The map ordering appears to be working correctly, and when new items are added they are entered at the expected position automatically if you were to iterate over the map. For instance for a map of std::string values:
std::map<myKey, std::string> myMap;
myKey key1 = myKey(std::string("key1"), 1);
myKey key2 = myKey(std::string("key2"), 2);
myKey key3 = myKey(std::string("key3"), 3);
myKey key4 = myKey(std::string("key4"), 4);
myMap[key1] = std::string("value1");
myMap[key2] = std::string("value2");
myMap[key3] = std::string("value3");
myMap[key4] = std::string("value4");
Would result in the following map key-value pairs at respective indexes:
[0] { addr = "key4", priority = 4 }, { "value4" }
[1] { addr = "key3", priority = 3 }, { "value3" }
[2] { addr = "key2", priority = 2 }, { "value2" }
[3] { addr = "key1", priority = 1 }, { "value1" }
However...I am having problems when it comes to modifying an existing priority of a key that is already present in the map.
In this situation, find() and [] (with respect to std::map) don't work as I want them to:
myKey modified_key1 = myKey(std::string("key1"), 5);
// problem 1 - this does not return iterator to "key1",
// but instead to end of the map
auto & foundKey = myMap.find(modified_key1);
// problem 2 - this adds a brand new item to the map
myMap[modified_key1] = std::string("value1");
After problem 2 as mentioned above, I am getting a new item added to the map with the same addr of an existing item. The new item appears to be added in the expected position based on the new (modified) priority, but the existing item to be updated remains as it was. So I end up with 2 items in the map with the same addr in their keys:
[0] { addr = "key1", priority = 5 }, { "value1" }
[1] { addr = "key4", priority = 4 }, { "value4" }
[2] { addr = "key3", priority = 3 }, { "value3" }
[3] { addr = "key2", priority = 2 }, { "value2" }
[4] { addr = "key1", priority = 1 }, { "value1" }
This is a problem for me as I would like to still rely on the notion that the addr of the map item key is unique.
What I want is the map to realise it already has an item with the same key (or more to the point the same key addr) and to re-order the item accordingly.
I have tried experimenting with compare functors as part of the map definition, and also overloading the keys == operator, but the same problem persists.
What am I missing or should I be approaching this differently?
The problem is that your comparison operator implemented incorrectly, it does not provide strict weak ordering hence undefined behavior of the std::map, lets say you have 3 objects of MyKey:
MyKey mk1{ "a",3 }, mk2{ "b", 2 }, mk3 { "a", 1 };
mk1 < mk2 -> true as 3 > 2
mk2 < mk3 -> true as 2 > 1
mk1 < mk3 -> false as addr is the same, but must be true
live example
I do not think your problem is easily solvable with std::map. Possible solution is to use boost::multi_index with address as one index and priority as another. To change priority of existing element boost::multi_index provides method to replace data.
Instead of MyKey you can use std::tuple<int, std::string>, it defines the relational operators for you:
using MyKey = std::tuple<int, std::string>;
Saves you a dozen of lines.
You cannot modify keys of elements in any associative containers. Instead, you need to remove the element using the old key and re-insert it with a new key.
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 have a nested JSON object. I'm trying to build it in a function and add the inner object to the original, but I can't extract the result.
void build_object (Poco::JSON::Object * const result)
{
/* Construct some int/bool/string fields here */
Poco::JSON::Object inner;
inner.set("some_number", 5);
inner.set("some_string", "xyz");
/* This is where it breaks down */
std::string key = "new_object";
result->set("new_object", inner);
/* Then some debugging and testing */
// The new object is printed inside the first -> seems like it's working
result->stringify(std::cout);
printf("result has(key): %i\n", result->has(key)); // true
printf("isObject: %i\n", result->isObject(key)); // false - huh?
printf("isNull: %i\n", result->isNull(key)); // false
printf("isArray: %i\n", result->isArray(key)); // false
Poco::JSON::Object::Ptr ptr = result->getObject(key);
// unsurpisingly fails since the above indicates it's not an object
printf("ptr isNull: %i\n", ptr.isNull()); // true
// ptr->has("some_number"); // throws NullPointerException
// if it's not an object/null/array, it must be a value
Poco::Dynamic::Var v = result->get(key);
// at least one of these things should be true, otherwise what is it?
printf("var isString: %i\n", v.isString()); // false
printf("var isStuct: %i\n", v.isStruct()); // false
printf("var isEmpty: %i\n", v.isEmpty()); // false
printf("var isArray: %i\n", v.isArray()); // false
printf("var isSigned: %i\n", v.isSigned()); // false
printf("var isNumeric: %i\n", v.isNumeric());// false
}
So, I have an inner object that is correctly put into the result, it is being printed via stringify with all the correct values and result->has() is successful. But, according to the result, it is not an object, array, or null, so you should be able to get it with var. But, once it's gotten from var, it's not a string, struct, array, or number, and it's also not empty. The inner object seems to exist and not exist at the same time.
So, how do I put this object into my result? And how do I get it out?
Thanks
note: I've seen this thread Correct usage of Poco C++ JSON for parsing data, but it's building the JSON object from string and then parsing it. I suppose I could build everything as a string and convert to the Poco Object at the last step, but I'm still curious why the above is happening. Also, using result->set() and result->get() are a cleaner, less hack-y solution than going through a string.
References: Poco JSON Doc,
Poco Dynamic Var Doc
Poco::JSON Objects and Arrays are held as shared pointers internally by default (optimization to avoid values copying) and everything is Dynamic::Var, so it works for both pointers and values. When you insert an Object as value it works because Dynamic::Var will hold pretty much anything, but the problem you experience when inspecting it comes from the fact that internal comparison does not return true for Object values because it compares only with default type - Poco::SharedPtr<Poco::JSON::Object>.
Here's a workaround:
void build_object (Poco::JSON::Object * const result)
{
// smart pointer, so don't worry about cleaning up
Poco::JSON::Object::Ptr inner = new Poco::JSON::Object;
inner->set("some_number", 5);
inner->set("some_string", "xyz");
std::string key = "new_object";
result->set(key, inner);
printf("isObject: %i\n", result->isObject(key)); // true
}
I have opened a github issue to alleviate this caveat.
I have been trying to create json file having nested object using poco library. Finally able to do with Poco::Json::Array.
Please find the posted code sinippet. Hope it will help. Json output attached with post.
#include "Poco\JSON\JSON.h"
#include "Poco\JSON\Stringifier.h"
#include "Poco\JSON\Object.h"
#include "Poco\Dynamic\Var.h"
using namespace std;
using Poco::JSON::Stringifier;
using Poco::JSON::Object;
using Poco::JSON::Array;
void makeJsonNestedObject()
{
Object RootObj(true);
Array FLArray;
for(int i=0; i<3; i++)
{
Object::Ptr FirstLevelArrayNode = new Poco::JSON::Object(true);
TCHAR strNameBuff[15];
_stprintf(strNameBuff, _T("%s_%d"),_T("Servername"),i);
std::basic_string<TCHAR> strName = strNameBuff;
FirstLevelArrayNode->set("HostName", strName);
FirstLevelArrayNode->set("Overall Impact", "Dummy Data");
Array SLArray;
for(int j=0; j<3;j++)
{
Object::Ptr SecondLevelArrayNode = new Poco::JSON::Object(true);
TCHAR attr1NameBuff[15];
TCHAR attr2NameBuff[15];
_stprintf(attr1NameBuff, _T("%s_%d"),_T("AttrOne"),j);
_stprintf(attr2NameBuff, _T("%s_%d"),_T("AttrTwo"),j);
std::basic_string<TCHAR> attr1Name = attr1NameBuff;
std::basic_string<TCHAR> attr2Name = attr2NameBuff;
SecondLevelArrayNode->set("Attribute", attr1Name);
SecondLevelArrayNode->set("SubAttribute", attr2Name);
Poco::Dynamic::Var obj(SecondLevelArrayNode);
SLArray.add(obj);
}
FirstLevelArrayNode->set("Attribute_Details",SLArray);
Poco::Dynamic::Var FLArrayNodeobj(FirstLevelArrayNode);
FLArray.add(FLArrayNodeobj);
}
std::ostringstream os;
std::cout <<"before stringlify.." << std::endl;
FLArray.stringify(os, 2);
std::cout << os.str() << std::endl;
}
Json output:
[
{
"HostName" : "Servername_0",
"Overall Impact" : "Dummy Data",
"Attribute_Details" : [
{
"Attribute" : "AttrOne_0",
"SubAttribute" : "AttrTwo_0"
},
{
"Attribute" : "AttrOne_1",
"SubAttribute" : "AttrTwo_1"
},
{
"Attribute" : "AttrOne_2",
"SubAttribute" : "AttrTwo_2"
}
]
},
{
"HostName" : "Servername_1",
"Overall Impact" : "Dummy Data",
"Attribute_Details" : [
{
"Attribute" : "AttrOne_0",
"SubAttribute" : "AttrTwo_0"
},
{
"Attribute" : "AttrOne_1",
"SubAttribute" : "AttrTwo_1"
},
{
"Attribute" : "AttrOne_2",
"SubAttribute" : "AttrTwo_2"
}
]
},
{
"HostName" : "Servername_2",
"Overall Impact" : "Dummy Data",
"Attribute_Details" : [
{
"Attribute" : "AttrOne_0",
"SubAttribute" : "AttrTwo_0"
},
{
"Attribute" : "AttrOne_1",
"SubAttribute" : "AttrTwo_1"
},
{
"Attribute" : "AttrOne_2",
"SubAttribute" : "AttrTwo_2"
}
]
}
]