QJsonArrays are not properly retrieved from QJsonObject - c++

In the project I'm currently working on there I use Qt's JSON functionality to store the state of a graph, where every component of the system recursively calls the toJson-functions of its members for serialization. This works fine, but I run into a weird issue when deserializing the JSON file.
As a test that illustrates the problem, I've created the following example code:
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtDebug>
int main() {
auto document{QJsonDocument::fromJson("{\n"
" \"o1\" : {\n"
" \"i1\" : [\"l1\", \"l2\", \"l3\"],\n"
" \"i2\": 3.14\n"
" },\n"
" \"o2\" : {\n"
" \"i2\": 2.718,\n"
" \"i1\" : [\"l1\", \"l2\", \"l3\"]\n"
" }\n"
"}")};
qDebug() << "is the document an object:" << document.isObject();
auto document_object{document.object()};
auto object_1_value{document_object.value("o1")};
qDebug() << "is o1 an object:" << object_1_value.isObject();
auto object_1{object_1_value.toObject()};
auto inner_1_value{object_1.value("i1")};
qDebug() << "is i1 an array:" << inner_1_value.isArray();
auto inner_1{inner_1_value.toArray()};
qDebug() << "i1:" << inner_1;
auto inner_1_inner_value{inner_1.at(0)};
qDebug() << "is i1[0] an array:" << inner_1_inner_value.isArray();
auto inner_1_inner{inner_1_inner_value.toArray()};
qDebug() << "i1[0]:" << inner_1_inner;
return 0;
}
Here, I am first querying o1, then try to get the array stored under i1.
However, I get the following output:
is the document an object: true
is o1 an object: true
is i1 an array: true
i1: QJsonArray([["l1","l2","l3"]])
is i1[0] an array: true
i1[0]: QJsonArray([["l1","l2","l3"]])
It seems like Qt stores the returned array into a useless one-element array; in my other code, at(0) solved the issue, but here even that does not work.
I would be very glad if someone could help me find a solution to reliably (and preferably hacklessly) read nested JSON arrays with Qt, as I truly do not understand the issue.
I am on Linux 5.6.11 with gcc 9.3.0 and Qt 5.14.2.

Your problem is the brace initialization. When you do assignment-style initialization instead, it works.
// instead of
auto inner_1{inner_1_value.toArray()};
// use
auto inner_1 = inner_1_value.toArray();
// same for all other brace inits
What I think happens is the classic clash between brace initialization and list initialization via a std::initializer_list constructor, in this case this one:
QJsonArray::QJsonArray(std::initializer_list<QJsonValue> args);
You want to brace-init a QJsonArray, but std::initializer_list takes precedence and what you actually instantiate is a QJsonArray with one item which is again a QJsonArray.
Often you see this problem explained with std::vector<int> and the two clashing ctors:
vector(size_type count);
vector(std::initializer_list<int> init);

Related

Understanding what QHash does when key not found

Note: You can find a minimal working example at the end of this post.
I'm using Qt 5.7. Let's say I have the following QHash:
QHash<HashKey, HashValue> hm;
with
enum HashKey {
K1,
K2,
K3,
K4,
K5
}
and
class HashValue {
public:
int x;
HashValue(int x) {
this->x = x;
}
}
I have initialized the hash map like this:
hm.insert(K1, HashValue((int)K1));
hm.insert(K2, HashValue((int)K2));
hm.insert(K3, HashValue((int)K3));
hm.insert(K4, HashValue((int)K4));
hm.insert(K5, HashValue((int)K5));
I have tested it by calling
cout << hm.value(K4).x << endl;
cout << hm.find(K4).value().x << endl;
Both return the same result that is 3. Now I tried doing the same with a key that is not part of the hash map by casting an integer to HashKey and calling the above two methods on it:
cout << hm.value(static_cast<HashKey>(100)).x << endl;
cout << hm.find(static_cast<HashKey>(100)).value().x << endl;
What I got is 8 (for the first call with value().x) and 5 (for the second call with find(...).value().x)
The docs states that
If there is no item with the specified key in the hash, these
functions return a default-constructed value.
I followed the link for default-constructed value and got the following:
[...] for example, QVector automatically initializes its items with
default-constructed values, and QMap::value() returns a
default-constructed value if the specified key isn't in the map. For
most value types, this simply means that a value is created using the
default constructor (e.g. an empty string for QString). But for
primitive types like int and double, as well as for pointer types, the
C++ language doesn't specify any initialization; in those cases, Qt's
containers automatically initialize the value to 0.
In my case this would mean a HashValue() call. However the fact that I get different results is baffling to say the least. I would expect to get the same result though the docs don't mention what find(...) does when an invalid key is passed as argument. All it says it finds the first occurrence of that key and returns an iterator (obviously since I call value() on it in the call above).
The quoted doc snippet from above is followed (again back to the document for QHash) by
If you want to check whether the hash contains a particular key, use
contains()
I can deal with having to call contains() every time I query my hash map though this means making two function calls - first to check if key is present and then to call value(...) to get the actual value if a valid entry is found. The call below returns "Key 100 not found":
cout << (hm.contains(static_cast<HashKey>(100)) ? "Key 100 found" : "Key 100 not found") << endl;
I would expect this check to be done internally but obviously this doesn't happen (my guess would be to prevent some performance impact on the querying functionality of this container).
The question here is why is all this happening and what is actually happening underneath all that?
Here is the project and the code for it:
HashTest.pro
QT += core
QT += gui
CONFIG += c++11
TARGET = HashTest
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
main.cpp
#include <QCoreApplication>
#include <QHash>
#include <iostream>
using namespace std;
enum HashKey {
K1 = 0,
K2 = 1,
K3 = 2,
K4 = 3,
K5 = 4
};
class HashValue {
public:
int x;
HashValue(int x) { this->x = x; }
HashValue() {}
};
int main(int argc, char *argv[])
{
QHash<HashKey, HashValue> hm;
hm.insert(K1, HashValue((int)K1));
hm.insert(K2, HashValue((int)K2));
hm.insert(K3, HashValue((int)K3));
hm.insert(K4, HashValue((int)K4));
hm.insert(K5, HashValue((int)K5));
cout << hm.value(K4).x << endl;
cout << hm.value(static_cast<HashKey>(100)).x << endl;
cout << hm.find(K4).value().x << endl;
cout << hm.find(static_cast<HashKey>(100)).value().x << endl;
cout << (hm.contains(static_cast<HashKey>(100)) ? "Key 100 found" : "Key 100 not found") << endl;
return a.exec();
}
The value() function is basically just for accessing values not checking if you have a one.
It returns a value and there is no way to indicate whether the value is "invalid" or not. So the choice if the design was to construct one. Qt could as an alternative throw an exception but this is not done here for several reasons (same as the containers of the c++ standard library btw.).
Secondly:
You are kind of using find() in a wrong way.
With find you can check whether the key is in the list and if not it point to the end() iterator of the hash.
QHash< Key,Value >::const_iterator valueIt = hash.find(<something>)
if(valueIt == hash.end())
{ // not found. error handling etc.
}
Value value = valueIt.value();
This is usually the "standard" way to check if a key exists and access it in a Map/Hash/Set/....
So when you use
find(...).value();
you could possibly access the end() iterator which causes undefined behavior.

Boost R-tree : unable to remove values?

First, my code:
// Reads the index
bi::managed_mapped_file file(bi::open_or_create, indexFile.c_str(), bf::file_size(indexFile.c_str()));
allocator_t alloc(file.get_segment_manager());
rtree_t * rtree_ptr = file.find_or_construct<rtree_t>("rtree")(params_t(), indexable_t(), equal_to_t(), alloc);
std::cout << "The index contains " << rtree_ptr->size() << " entries." << std::endl;
std::ifstream inf(transFile.c_str());
std::string line;
while(getline(inf,line))
{
transition t = transition(line);
point A;
A.set<0>(t.getQ1mz()-1);
A.set<1>(t.getQ3mz()-1);
A.set<2>(0.3);
A.set<3>(0.2);
value_t v = std::make_pair(A,t);
rtree_ptr->insert(v);
rtree_ptr->remove(v);
}
std::cout << "Finished. The index now contains " << rtree_ptr->size() << " entries." << std::endl;
It reads the R-tree from a memory-mapped file. Then, it reads an input file, transFile, make ten so-called "transition" objects from it's content, and inserts them in the tree. Immediately after, it removes them. This is a useless case, but it illustrates well the problem that the removal steps don't work. The output I get is :
The index contains 339569462 entries.
Finished. The index now contains 339569472 entries.
So clearly, the size of the tree increases by ten, because the ten insertions worked like a charm ; but if the removals were working, in the end the tree should have the same size as before, which is not the case.
I have followed the syntax about removing values from an R-tree described here, and all compiles properly, but for some strange reason it just doesn't remove the value. My guess might be that since it deletes by value, it might just not find the value to delete, but how can it be since the value is the one just inserted one line ago?

How do you determine what kind of node you are dealing with in yaml-cpp?

I'm reading the tutorial code here: https://code.google.com/p/yaml-cpp/wiki/Tutorial
One example goes like this:
YAML::Node primes = YAML::Load("[2, 3, 5, 7, 11]");
for (YAML::const_iterator it=primes.begin();it!=primes.end();++it) {
std::cout << it->as<int>() << "\n";
}
And the next like this:
YAML::Node lineup = YAML::Load("{1B: Prince Fielder, 2B: Rickie Weeks, LF: Ryan Braun}");
for(YAML::const_iterator it=lineup.begin();it!=lineup.end();++it) {
std::cout << "Playing at " << it->first.as<std::string>() << " is " << it->second.as<std::string>() << "\n";
}
However, if you swap the YAML files between these two cases, you will get an error, as you are accessing a map iterator for a sequence or vice versa:
terminate called after throwing an instance of 'YAML::InvalidNode'
what(): yaml-cpp: error at line 0, column 0: invalid node; this may result from using a map iterator as a sequence iterator, or vice-versa
For an arbitrary YAML input, how can I determine whether I am dealing with a sequence or a map in the loop (ie whether I should be using ->first or not) without using a try/catch block?
I tried looking for the documentation, but I could not find it.
UPDATE:
This is what I am trying to do:
YAML::Node config = YAML::LoadFile(filename);
for (YAML::const_iterator it=config.begin();it!=config.end();++it) {
if (it->Type() == YAML::NodeType::Map) { // exception
std::cout << it->first.as<std::string>();
} else if (it->Type() == YAML::NodeType::Sequence) {
std::cout << it->as<std::string>();
}
}
But when I run the code I get the exception as above. It compiles fine.
I am using the yaml-cpp which comes with ubuntu 14.04 (0.5.1).
You can either
switch (node.Type()) {
case Null: // ...
case Scalar: // ...
case Sequence: // ...
case Map: // ...
case Undefined: // ...
}
or query explicitly, e.g.:
if (node.IsSequence()) {
// ...
}
(I added this bit to the Tutorial.)
Edit: In your specific example, you should check config.Type() before you iterate, not the type of any of the nodes during your iteration.

C++ Map returning class object based on variable key

I reacently learned about map structures and I am trying to use one, but can't seem to solve one problem.
I have tried the code below:
map<string, valuePair> translator;
The class valuePair is just a combination of 2 objects (string and a number).
Im assigning values to the map
translator[currentWord] = valuePair(stateNo, "state");
Where currentWord is a variable string, stateNo is an int.
Now later I want to get back the valuePair number value from the map, but can;t seem to be able to do it.
Heres a screenshot of my watch window trying to access the variable x.
http://i.imgur.com/m3MOgi2.png
These are all the ways I managed to find online to return the value, yet none of them seem to work. As you can see the key "a" is in the map. What am I doing wrong?
[EDIT] Thanks guys, I used the tips you gave in comments and found out, that actually it works the way I expected - translator["a"].x prints the values I need. I have nothing to mark as "Correct answer" though, and I'm not sure what to do with this thread now :/
As Jonathan Henson posted in the comments, you would be better off posting your code than your debugger output. Ideally you want a minimal example that reproduces the error you are having.
Based on the debugger output, I'm wondering if you have a scope issue -- you are trying to access the map data outside of the scope where you have it defined. Without seeing the source code though, there is no way to know.
Here is a working example that does precisely what you are trying to do. The only modification is I have used a struct for valuePair, and c++ 11 initializer lists. This won't affect the map access code, but you might need to turn on c++ 11 support to compile it.
As a first step, look check it out in your debugger and see if you get the same difficulty. If so, your problem is the debugger or debugger setup, not your code.
If the debugger works for the sample code (posted below), gradually transform my code into your code (making minimal changes, building, and see if it still works). This is a very useful approach to learning the fine points of a language.
#include <iostream>
#include <map>
#include <string>
using namespace std;
struct valuePair
{
int num;
string str;
};
int main()
{
map<string, valuePair> translator;
translator["a"] = {0,"bla"};
translator["b"] = {1, "alb"};
translator["c"] = {2, "lab"};
valuePair res = translator["c"];
cout << "c:" << res.num << "," << res.str << "\n";
res = translator.at("b");
cout << "b:" << res.num << "," << res.str << "\n";
res = translator.find("a")->second;
cout << "a:" << res.num << "," << res.str << "\n";
return 0;
}
If you have an std::map<K,V> m; you can add elements and change values by using m[k] = v;. However the operator[] is an operation which always creates a key/value pair, if the key you are looking for is not contained in the map. Thus it is not allowed when you have a const reference or pointer, e.g. const std::map<K,V>&.
With a std::map you always have to consider the case that the key you are looking for is actually not contained in the map!
To look for the value stored under a given key you have to use std::map::find (link).
Example:
std::map<std::string,valuePair>::const_iterator it = translator.find(currentWord);
if(it != translator.end()) {
// the map contains an element with this key
const valuePair& value = it->second; // this is the value
}
else {
// the map *does not* contain an element with this key
}
As mentioned in the comments std::map::at (link) may be an alternative for C++11. But then you have to take care of the possible exception which is thrown when you use a key which does not exist in the map.
this works for me
#include <map>
#include <sstream>
#include <string>
int main()
{
std::map<std::wstring, double> items;
items[L"0"] = 0.123;
items[L"1"] = 1.234;
items[L"2"] = 2.234;
items[L"3"] = 3.345;
items[L"4"] = 4.567;
for (int i = 0; i < 5; i++)
{
std::wstringstream oss;
oss << i;
std::wstring key = oss.str();
double value = items[key];
}
return 0;
}

Not the address I want content?

I'm just getting more and more confused by wasting more time on my code. I just want the content of the iterator, not its address. Here is my code:
Peptides tempPep;
tempPep.set_PEPTIDE("AABF");
std::vector<Peptides>::iterator itPep = std::find_if (this->get_PepList().begin(), this->get_PepList().end(),boost::bind(&Peptides::Peptide_comparison, _1,tempPep));
if (itPep != this->get_PepList().end())
{
Spectra tempSp;
tempSp.set_Charge(1127);
tempSp.set_Snum(1);
std::cout << "without iterator "<< this->get_PepList()[0].get_New_S_num() << std::endl;
// output -> 0
std::cout << "with iterator" << itPep->get_New_S_num() <<std::endl;
//output -> 1129859637
}
Try changing your code to the following:
std::vector<Peptides> p = this->get_PepList();
std::vector<Peptides>::iterator itPep = std::find_if (p.begin(),
p.end(),boost::bind(&Peptides::Peptide_comparison, _1,tempPep));
If you want the content, point to it: *itPep
An iterator overloads the * operator and returns the data. (thanks for the correction, I did not know that!)