QT: Arbitrarily complex data structures with QVariant - c++

Quoting the QT Docs:
You can even store QList and QMap values in
a variant, so you can easily construct arbitrarily complex data
structures of arbitrary types. This is very powerful and versatile,
but may prove less memory and speed efficient than storing specific
types in standard data structures.
Does anyone know of, or have, an example of doing exactly this?
I'm a long time C++ programmer, but a QT Nube, and the copy of write semantics are giving me fits. Maps and Lists of QVariants data structures seems immutable. Every time I try to modify a tree of values, I just end up modifying a copy.
Got some feedback from my first post that I should add an example. Here goes:
// Input Data:
//
// { "f1" : "field-1",
// "list" : [ 0, 1, 2, 3, 4 ] }
//
// Convert the data, commented above, into a QVariantMap with two
// values:
// "f1" - a string
// "list" - a QVariantList of integers
QVariant vData = ConvertJsonDocument(document);
// Dump
qWarning( VariantToString(vData).toLocal8Bit() );
// Convert vData to QVariantMap
QVariantMap vMap = vData.value<QVariantMap>();
// Get the list of integers as a QVariantList
QVariantList vList = vMap["list"].value<QVariantList>();
// Change the 0 to a 5
vList[0] = 5;
// Dump
qWarning( VariantToString(vData).toLocal8Bit() );
Output from above:
{ "f1" : "field-1", "list" : [ 0, 1, 2, 3, 4 ] }
{ "f1" : "field-1", "list" : [ 0, 1, 2, 3, 4 ] }
DESIRED output from above:
{ "f1" : "field-1", "list" : [ 0, 1, 2, 3, 4 ] }
{ "f1" : "field-1", "list" : [ 5, 1, 2, 3, 4 ] }
I get that I am modifying copies, but for the life of my I can't figure out how NOT to. How do I edit the original source data? (The data in the tree rooted at vData.)

Once you make the desired alterations, you need to go back through the tree and update your variables with the new data.
// Convert vData to QVariantMap
QVariantMap vMap = vData.value<QVariantMap>();
// Get the list of integers as a QVariantList
QVariantList vList = vMap["list"].value<QVariantList>();
// Change the 0 to a 5
vList[0] = 5;
// Change the map using insert, which replaces the value
vMap.insert("list", vList);
// Rebuild the QVariant from the QMap
vData = QVariant::fromValue(vMap);
// Dump
qWarning( VariantToString(vData).toLocal8Bit() );
You can convert the data back from the QVariant and update the source document from there.

Related

nlohmann json insert value array partially to already existing data

I have a particular case which I am trying to solve with minimal changes if possible.
one of the data is
js["key1"]["subkey2"]["subsubkey3"].push_back({1,2,3,{4,5}});
[ 1,2,3,[[4,5]] ]
Later at some stage I want to insert
{1,2,3,{4,6}}
Then it should become
[ 1,2,3,[[4,5],[4,6]] ]
How can I make this possible without making 1,2,3 value as key?
I did some playing. I didn't get the results you were looking for. Here's my code and results so far.
#include <iostream>
#include <json.hpp>
using namespace std;
using JSON = nlohmann::json;
int main() {
JSON json = JSON::object();
JSON key1JSON = JSON::object();
JSON key2JSON = JSON::object();
JSON key3JSON = JSON::array();
key3JSON.push_back( {1,2,3, {4,5} } );
key3JSON.push_back( {6} );
key2JSON["subsubkey3"] = key3JSON;
key1JSON["subkey2"] = key2JSON;
json["key1"] = key1JSON;
cout << json.dump(2) << endl;
}
Output:
{
"key1": {
"subkey2": {
"subsubkey3": [
[
1,
2,
3,
[
4,
5
]
],
[
6
]
]
}
}
}
You'll see that the first push_back pushed an array inside an array, which is probably one level deeper than you wanted, and the second one added a second array, which is also not what you want.
Which means you're probably going to have to write your own method, especially as you want to also handle uniqueness. I personally never free-format data that way you have in your example. But maybe your method would look something like:
bool contains(const JSON &json, const JSON &value) {
... this looks like fun to write.
}
void appendUnique(JSON &json, const JSON &array) {
for (JSON & thisJson: array) {
if (!contains(json, thisJson)) {
json.push_back(thisJson);
}
}
}
I modified my code like this:
void appendUnique(JSON &json, const JSON & array) {
for (const JSON & thisJSON: array) {
json.push_back(thisJSON);
}
}
...
appendUnique(key3JSON, {1,2,3, {4,5} } );
appendUnique(key3JSON, {6} );
And got this:
{
"key1": {
"subkey2": {
"subsubkey3": [
1,
2,
3,
[
4,
5
],
6
]
}
}
}
I'm not going to write the isUnique method. But I think you may have to take this to conclusion.

How to control and modify the std::map ordering using a user-defined key

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.

How do I increment value of an entry if it already exists in the PQ?

I'm using this question as a starting point:
Add Key and Value into an Priority Queue and Sort by Key in Java
I have a similar situation, where I have a PQ of my POJO that has two important attributes: key and value.
However, before I add a new entry into the PQ, first I want to check if an entry with the same key exists already in the PQ. In that case, I want to increment its value instead. How do I go about doing this?
The javadoc says that.
It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y))
But again, this is not necessary. What it is necessary is to override the method public boolean equals(Object entry) for the method contains() to work, instead you are declaring the method public boolean equals(Entry entry).
You should have an equals() similar to.
#Override
public boolean equals(Object entry) {
if (entry instanceof Entry) return this.key == ((Entry) entry).key;
else return false;
}
Another thing to consider it that it is a terrible idea to mutate the object when this is already in a sorted/hashed collection. This will cause strange behavior. I'll show you an example.
Using this a toString() method in your entry.
#Override
public String toString() {
return String.format("{ %d, %d }", key, value);
}
Using this code to print the priority queue.
PriorityQueue<Entry> queue = new PriorityQueue<>();
for (Entry data : entries) {
Entry entry = new Entry(data.getKey(), data.getValue());
if (queue.contains(entry)) {
for (Entry current : queue) { // just for the example
if (current.equals(entry)) {
current.addToValue(entry.getValue());
}
}
} else {
queue.add(entry);
}
}
while (!queue.isEmpty()) // print ordered
System.out.println(queue.poll());
With this data.
List<Entry> entries = Arrays.asList(
new Entry(1, 4),
new Entry(2, 3),
new Entry(2, 5)
);
The output is not correctly sorted { 1, 4 }, { 2, 8 } instead { 2, 8 }, { 1, 4 }, this because the entry with id 2 was mutated after it was added to the collection.
By the other hand, with this data
List<Entry> entries = Arrays.asList(
new Entry(1, 4),
new Entry(2, 8)
);
It prints the output correctly sorted { 2, 8 }, { 1, 4 }.
The solution could be using a HashMap and then a TreeSet.
Map<Integer, Integer> map = new HashMap<>();
for (Entry data : entries) { // here instead read from csv and create entry
map.computeIfPresent(data.getKey(), (k, v) -> v + data.getValue()); // sum priorities
map.putIfAbsent(data.getKey(), data.getValue()); // add when not exists
}
Now that you have a map identified by the key and containing the sumarized values of the priorities, you can use a TreeSet or a PriorityQueue to sort the entries.
You don't need to use your custom Entry, you can use the java Entry and pass a Comparator to the TreeSet / PriorityQueue.
TreeSet<Map.Entry<Integer, Integer>> treeSet = new TreeSet<>((e1, e2) ->
e2.getValue() - e1.getValue() != 0 ?
e2.getValue() - e1.getValue() :
e2.getKey() - e1.getKey());
treeSet.addAll(map.entrySet());
System.out.println(treeSet);
This comparator compares first the priority, if is different, return -1 or 1, following a descendant order.
If the priority is the same, it compares the key, and returns -1 or 1 (0 is not possible because there are not duplicate keys) and order of the keys is descending.

V8, append text to the last element of the array

I'm building a native NodeJS C++ module based on V8. I got the following code in loop:
Local<Array> nodes = Array::New();
/********** INSIDE THE LOOP ************/
Local<Object> node_obj = Object::New();
node_obj->Set(data_symbol, String::New(input.substr(openPos + (lastTag > 1 ? 3 : 2), pos - openPos - (lastTag > 1 ? 3 : 2) - 1).c_str()));
node_obj->Set(tag_symbol, Integer::New(lastTag));
nodes->Set(id, node_obj);
And I'm populating an array with objects, so the output (in JS) will look like this:
[
{tag: 2, data: "asdsadsadasfddgdfgdfg"},
{tag: 1, data: "afg235235232fgdfg"}
]
My questions is how I can append a string to the data_symbol of the last object of the array?
Full code can be found here: http://pastebin.com/tCgWCxyA
Example of what I'm trying to do:
Lets take this structure for example:
struct Node {
short tag;
std::string data;
Node(const std::string& input, short tagId) : tag(tagId), data(input) {}
};
std::vector<Node> elems;
My question is how I can do
elems.back().data.append("SomeString");
in V8?
You can use String::Concat(Handle<String> left, Handle<String>right) like so:
HandleScope scope;
Local<Object> lastnode = nodes->Get(nodes->Length() - 1)->ToObject();
Local<String> lastdatastr = lastnode->Get(data_symbol)->ToString();
lastnode->Set(data_symbol,
String::Concat(lastdatastr, String::New(" I'm appended!")));

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;
}