How to retrieve integer value from JSON data in Qt - c++

How can I retrieve the integer value from the QJsonValue object? Suppose that I have the following JSON data:
{
"res": 1,
"message": "Common error"
}
I need to extract the "res" value from this data, so I've tried to use the following code:
QJsonDocument d = QJsonDocument::fromJson(some_json_data.toUtf8());
QJsonObject root_object = d.object();
QJsonValue res = root_object.value("res");
But I've found that QJsonValue class doesn't have a member function toInt or something like this (there's toDouble, toString, etc only). What can I do in such a situation? What is the best way to extract integer values via QjsonValue class?

(tl;dr: a one-liner solution at the end of answer.)
First a comprehensive way giving you full control. Below code assumes int is enough range for your integers, but could be expanded to work for most of the range of int64_t (but better test the boundary cases to get it fully correct):
QJsonValue res = root_object.value("res");
int result = 0;
double tmp = res.toDouble();
if (tmp >= std::numeric_limits<int>::min() && // value is not too small
tmp <= std::numeric_limits<int>::max() && // value is not too big
std::floor(tmp) == tmp // value does not have decimals, if it's not ok
) {
result = std::floor(tmp); // let's be specific about rounding, if decimals are ok
} else {
// error handling if you are not fine with 0 as default value
}
A shorter way using QVariant, and as an example also getting result into a larger integer type, if you just want to let Qt do it's thing. I'm not sure how it it handles integer values which are too big for a double to handle accurately, so again if that is important, better test.
QJsonValue res = root_object.value("res");
QVariant tmp = res.toVariant();
bool ok = false;
qlonglong result = tmp.toLongLong(&ok);
if (!ok) {
// error handling if you are not fine with 0 as default value
}
or same as error-ignoring one-liner, change integer type as appropriate:
qlonglong result = root_object.value("res").toVariant().toLongLong();

int QJsonValue::toInt(int defaultValue = 0) const
Converts the value to an int and returns it.
If type() is not Double or the value is not a whole number, the
defaultValue will be returned.
QByteArray JSettings::read_file(QString path)
{
QFile file;
file.setFileName(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return QByteArray();
QByteArray barray = file.readAll();
file.close();
return barray;
}
QVariantMap JSettings::to_map(QByteArray json)
{
QJsonDocument jdoc = QJsonDocument::fromJson(json);
QVariantMap vmap = jdoc.toVariant().toMap();
// qDebug() << vmap["res"].toInt();
return vmap;
}
QJsonObject JSettings::to_object(QByteArray json)
{
QJsonDocument jdoc = QJsonDocument::fromJson(json);
QJsonObject obj = jdoc.object();
// qDebug() << obj["res"].toInt();
return obj;
}
Qt JSON portal: http://doc.qt.io/qt-5/json.html

QJsonDocument d = QJsonDocument::fromJson(some_json_data.toUtf8());
QJsonObject root_object = d.object();
QJsonValue res = root_object.value("res");
int i = res.toString().toInt();
this is working, QString to Int

Related

C++ Qt unable to parse JSON array correctly

I am trying to parse json with Qt but I have no success. This is the ouput that I get from the server:
[{"anni":2019},{"anni":2018},{"anni":2017}]
Where that's generated from this simple php:
header('Content-Type: application/json');
echo json_encode($data);
The $data is an array containing the values you see above. I am using this piece of code in Qt 5.11.2:
void MainWindow::showYears() {
//reply is a QNetworkReply* reply;
if (reply->error() != QNetworkReply::NoError) {
//some error managment
} else {
auto responsedata = reply->readAll();
QJsonArray years = QJsonDocument::fromJson(responsedata).array();
qDebug() << QString{responsedata};
for(const QJsonValue& y : years) {
QJsonObject obj = y.toObject();
//doing "qDebug() << r" shows that r is "" (empty!)
auto r = obj["anni"].toString();
ui->comboBoxP->addItem(r);
}
}
}
What's wrong here?
Please note that qDebug() << QString{responsedata}; prints "[{\"anni\":2019},{\"anni\":2018},{\"anni\":2017}]"
The value for your field anni is an integer. Using the member function toString will not convert it to a string representation. it will return NULL. http://doc.qt.io/qt-5/qjsonvalue.html#toString
Try with: auto r = QString::number(obj["anni"].toInt());

Serialize encrypted password to XML

I have an application that contains userbase which is stored in xml file and loaded at the start of the program. I use QXmlStreamWriter/Reader for this purpose. The problem occurs when I try to serialize encrypted (hashed?) form of password (using QCryptographicHash and Sha256 for this).
QCryptographicHash return QByteArray, which can be converted to QString (necessary for use of QXmlStreamWriter/Reader). Relevant code below. Everything works fine before the serialization (I can log in), but when I read data from xml, after finding hashed password the function behaves like it found the EOF, and only about 2 chars are loaded to the QString by QXmlStreamReader.
In the code please ignore reservations etc (it's a cinema panel), relevant fragments are passwords, I supply full function just in case.
I hope I explained what the issue is, here are fragments of my code (note: before adding the hashing everything worked fine)
Register function (the hashing, pass is a QString):
QString hash = QCryptographicHash::hash(pass.toUtf8(), QCryptographicHash::Sha256);
User* user_pointer;
user_pointer = new User(name, hash, admin);
Writing function:
QFile file("users/users.xml");
if(!file.open(QIODevice::WriteOnly))
throw "Error podczas otwierania bazy użytkowników!";
QXmlStreamWriter writer (&file);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeStartElement("USERS");
int list_size = userList.size();
for(int i = 0; i < list_size; i++)
{
writer.writeStartElement("USER");
writer.writeTextElement("name", userList.at(i)->name);
writer.writeTextElement("pass", userList.at(i)->password);
writer.writeTextElement("admin", QString::number(userList.at(i)->is_admin));
writer.writeStartElement("RESERVATIONS");
for(int m = 0; m < userList.at(i)->reservList.size(); m++)
{
writer.writeStartElement("reservation");
writer.writeTextElement("moviename", userList.at(i)->reservList.at(m)->movie_name);
writer.writeTextElement("date", userList.at(i)->reservList.at(m)->date.toString("dd.MM.yyyy"));
writer.writeTextElement("hour", (userList.at(i)->reservList.at(m)->hour).toString("hhmm"));
writer.writeTextElement("paid", QString::number(userList.at(i)->reservList.at(m)->paid));
for(int n = 0; n < userList.at(i)->reservList.at(m)->placeList.size(); n++)
writer.writeTextElement("place", QString::number(userList.at(i)->reservList.at(m)->placeList.at(n)));
writer.writeEndElement();
}
writer.writeEndElement();
writer.writeEndElement();
}
writer.writeEndDocument();
file.close();
}
Reading function:
QFile file("users/users.xml");
if(!file.open(QIODevice::ReadOnly))
throw "Brak bazy danych użytkowników lub błąd jej otworzenia!";
QXmlStreamReader reader;
reader.setDevice(&file);
reader.readNext();
QString user_name;
QString user_pass;
bool admin;
QString movie_name;
QTime hour;
QDate date;
bool paid;
User* user_pointer = NULL;
int user_counter = -1;
Reservation* reserv_pointer = NULL;
int reserv_counter = -1;
while(!reader.atEnd())
{
if(reader.isStartElement())
{
if(reader.name() == "USER")
{
reserv_counter = -1;
}
if(reader.name() == "name")
user_name = reader.readElementText();
if(reader.name() == "pass")
user_pass = reader.readElementText();
if(reader.name() == "admin")
{
admin = reader.readElementText().toInt();
user_pointer = new User(user_name, user_pass, admin);
userList.append(user_pointer);
user_counter++;
}
if(reader.name() == "reservation")
{
reserv_counter++;
}
if(reader.name() == "moviename")
movie_name = reader.readElementText();
if(reader.name() == "hour")
hour = QTime::fromString(reader.readElementText(), "hhmm");
if(reader.name() == "date")
date = QDate::fromString(reader.readElementText(), "dd.MM.yyyy");
if(reader.name() == "paid")
{
paid = reader.readElementText().toInt();
reserv_pointer = new Reservation(movie_name, date, hour, paid);
userList.at(user_counter)->reservList.append(reserv_pointer);
}
if(reader.name() == "place")
{
userList.at(user_counter)->reservList.at(reserv_counter)->placeList.append(reader.readElementText().toInt());
}
reader.readNextStartElement();
}
else
reader.readNext();
}
file.close();
}
The hash value is not a string, it is a sequence of arbitrary byte values, some of those might happen to be problematic when interpreting it as a string.
You have an implicit conversion from QByteArray to QString, for which the documentation says that:
The byte array is converted to Unicode using the fromUtf8() function.
This function stops conversion at the first NUL character found, or
the end of the ba byte array.
You could for example use explicit conversion which specifies the length:
QString::fromUtf8(byteArray.data(), length);
As Frank Osterfeld noted in the comments, using UTF8 is not a good idea, I have tested from and to Latin1 extensively for a project I was working on, and the binary data is identical, however in looks "funky" in textual form, which may not play well with the XML reading and writing, toHex() will fix that by limiting the character set to 0-F:
QByteArray b; // hash
QString ss = QString::fromLatin1(b.toHex()); // to QString
b = QByteArray::fromHex(ss.toLatin1()); // back to QByteArray
Your problem is that XML is a text format but encrypted passwords are a binary format. The two are not compatible. You need to have some way to encode the binary data in a text-friendly format.
As #ddriver has mentioned, one way to do this is to use QByteArray::toHex() since it will convert all bytes to human readable text characters. The cost, however, is a 100% increase in size (2 characters returned for each byte of the password hash)
Another, more ubiquitous, and efficient, method for transmitting binary data in textual form is to use the QByteArray::toBase64(). While it also returns binary data in a textual form, there's only a 33 1/3% increase in size (4 bytes returned for every 3 bytes of password hash).
(You might recognise this encoding since its the nonsensical text that usually ends with one or two = characters, and is the usual encoding used to transmit binary data in emails)

Rapidjson returning reference to Document Value

I'm having some trouble with the following method and I need some help trying to figure out what I am doing wrong.
I want to return a reference to a Value in a document. I am passing the Document from outside the function so that when I read a json file into it I don't "lose it".
const rapidjson::Value& CTestManager::GetOperations(rapidjson::Document& document)
{
const Value Null(kObjectType);
if (m_Tests.empty())
return Null;
if (m_current > m_Tests.size() - 1)
return Null;
Test& the_test = m_Tests[m_current];
CMyFile fp(the_test.file.c_str()); // non-Windows use "r"
if (!fp.is_open())
return Null;
u32 operations_count = 0;
CFileBuffer json(fp);
FileReadStream is(fp.native_handle(), json, json.size());
if (document.ParseInsitu<kParseCommentsFlag>(json).HasParseError())
{
(...)
}
else
{
if (!document.IsObject())
{
(...)
}
else
{
auto tests = document.FindMember("td_tests");
if (tests != document.MemberEnd())
{
for (SizeType i = 0; i < tests->value.Size(); i++)
{
const Value& test = tests->value[i];
if (test["id"].GetInt() == the_test.id)
{
auto it = test.FindMember("operations");
if (it != test.MemberEnd())
{
//return it->value; is this legitimate?
return test["operations"];
}
return Null;
}
}
}
}
}
return Null;
}
Which I am calling like this:
Document document;
auto operations = TestManager().GetOperations(document);
When I inspect the value of test["operations"] inside the function I can see everything I would expect (debug code removed from the abode code).
When I inspect the returned value outside the function I can see that it's an array (which I expect). the member count int the array is correct as well, but when print it out, I only see garbage instead.
When I "print" the Value to a string inside the methods, I get what I expect (i.e. a well formated json), but when I do it outside all keys show up as "IIIIIIII" and values that aren't strings show up correctly.
rapidjson::StringBuffer strbuf2;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer2(strbuf2);
ops->Accept(writer2);
As this didn't work I decided to change the method to receive a Value as a parameter and do a deep copy into it like this
u32 CTestManager::GetOperationsEx(rapidjson::Document& document, rapidjson::Value& operations)
{
(...)
if (document.ParseInsitu<kParseCommentsFlag>(json).HasParseError())
{
(...)
}
else
{
if (!document.IsObject())
{
(...)
}
else
{
auto tests = document.FindMember("tests");
if (tests != document.MemberEnd())
{
for (SizeType i = 0; i < tests->value.Size(); i++)
{
const Value& test = tests->value[i];
if (test["id"].GetInt() == the_test.id)
{
const Value& opv = test["operations"];
Document::AllocatorType& allocator = document.GetAllocator();
operations.CopyFrom(opv, allocator); //would Swap work?
return operations.Size();
}
}
}
}
}
return 0;
}
Which I'm calling like this:
Document document;
Value operations(kObjectType);
u32 count = TestManager().GetOperationsEx(document, operations);
But... I get same thing!!!!
I know that it's going to be something silly but I can't put my hands on it!
Any ideas?
The problem in this case lies with the use of ParseInSitu. When any of the GetOperations exist the CFileBuffer loses scope and is cleaned up. Because the json is being parsed in-situ when the buffer to the file goes, so goes the data.

cannot read string result from MYSQL

I want to have an if else statement inside a loop when getting the results of
data from mysql .. However the if statement cannot read the result.. but the codes are perfectly I see the problem in my if else condition.
here is my Code from my asynctask
for (int i = 0; i < markers.length(); i++) {
JSONObject c = markers.getJSONObject(i);
// Storing each json item in variable
Double LAT = c.getDouble(TAG_LAT);
Double LNG = c.getDouble(TAG_LNG);
String color = c.getString(TAG_STATUS);
String red = "ongoing";
String green = "firedout";
if (color == red){
LatLng position = new LatLng(LAT, LNG);
status.add(new MarkerOptions()
.title(color)
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))
.position(position));
// adding HashList to ArrayList
}
if (color == green){
LatLng position = new LatLng(LAT, LNG);
status.add(new MarkerOptions()
.title(color)
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))
.position(position));
// adding HashList to ArrayList
}
}
Equivalent String values are not guaranteed to be unique in Java. In other words, there can be two String objects with exactly the same value.
String s1 = "ongoing";
String s2 = new String(s1);
System.out.println(s1 == s2); // false!
System.out.println(s1.equals(s2)); // true :)
Therefore, you must say:
if (red.equals(color)) {
// do something
}

EXC_BAD_ACCESS issue after refactoring

The following code works in a the Marmalade simulator (I'm on OSX using x-code)
bool PictureDictionary::OnTableSelect(CTable* table, int tab){
//if something is selected, look up the item, and display it
//also change the search to the selected item
if(-1 < tab){
// if a term is selected, set the search text field to the term
CString term = m_SearchResults.GetString(tab);
if(m_currentWord != (char*)term.Get()){
m_currentWord = (char *)term.Get();
m_searchTextField->SetAttribute("text", term);
char* normalizedTerm = (char *)term.Get();
char* imagePath;
sprintf(imagePath,"images/%s.jpg", normalizedTerm);
if(m_ImageAttached){
m_Image->SetAttribute("image", (const char*)imagePath);
} else {
m_Image = CreateImage(CAttributes()
.Set("name", "picture")
.Set("x1", "0")
.Set("x2", "0")
.Set("y1", "50%")
.Set("image", (const char*)imagePath)
);
m_SearchView->AddChild(m_Image);
m_ImageAttached = true;
}
}
}
return true;
}
When I run the simulator, and select an item from the table, the image appears, and changes when I select a different item. When I go to refactor, I get a EXC_BAD_ACCESS (code=1…..) Error
bool PictureDictionary::OnTableSelect(CTable* table, int tab){
//if something is selected, look up the item, and display it
//also change the search to the selected item
if(-1 < tab){
// if a term is selected, set the search text field to the term
CString term = m_SearchResults.GetString(tab);
if(m_currentWord != (char*)term.Get()){
m_currentWord = (char *)term.Get();
m_searchTextField->SetAttribute("text", term);
char* normalizedTerm = (char *)term.Get();
char* imagePath;
sprintf(imagePath,"images/%s.jpg", normalizedTerm);
UpdatePictureView(imagePath);
}
}
return true;
}
void PictureDictionary::UpdatePictureView(char* imagePath){
if(m_ImageAttached){
m_Image->SetAttribute("image", (const char*)imagePath);
} else {
m_Image = CreateImage(CAttributes()
.Set("name", "picture")
.Set("x1", "0")
.Set("x2", "0")
.Set("y1", "50%")
.Set("image", (const char*)imagePath)
);
m_SearchView->AddChild(m_Image);
m_ImageAttached = true;
}
}
Any suggestions on how to clean up the code without getting these issues?
Edit RE Comments about uninitialized variables:
m_ImageAttached was initialized to false in the constructor, unless I'm doing something wrong. Also, changing the condition to check if m_Image!=NULL also throws the same error.
main.cpp:
PictureDictionary pictDict(myApp, &dictionary);
Constructor for PictureDictionary:
PictureDictionary::PictureDictionary(CAppPtr app,Dictionary::Dictionary* dictionary){
m_App = app;
m_Dictionary = dictionary;
m_currentWord = "";
m_ImageAttached = false;
}
imagePath is an unitialized pointer, in both snippets. Any attempt to dereference is undefined behaviour. It just appeared to work in the first snippet. Use an array or populate a std::string instead:
std::string imagePath(std::string("images/") + normalizedTerm + ".jpg");
And use std::string::c_str() if access to the underlying const char* is required.