how to parse associative JSON arrays in QT? - c++

I would like to know, how to parse associative JSON arrays in QT.
This is my example JSON:
{
"req_code": 5,
"params": {
"email":"user#domain.com",
"password":"123"
}
}
So, in order to get email (from JSON string called 'data') I would have to do something like this:
QJsonDocument doc=QJsonDocument::fromJson(data.toUtf8());
QJsonObject jobj=doc.object();
QJsonValue params_value=jobj.value(QString("params"));
QJsonArray params_array=params_value.toArray();
Now, 'email' is being held in 'params_array' object, but this array is not a QHash and not a QMap. If it would be a QHash I would get email by:
QString email=params_array.value("email");
But it is not a hash, it is a vector. So how do I get the value of 'email' property in this case in a proper and fast way?

The value of params is an object, and QJsonObject provides map-like functions so you can easily access it:
auto doc = QJsonDocument::fromJson(data.toUtf8());
auto docObj = doc.object();
auto paramsObj = docObj["params"].toObject();
auto email = paramsObj["email"];
And documentation says:
All JSON classes are value based, implicitly shared classes.
so you don't really need to take much care of performance. If you want to conver QJsonObject to hash or map, you can use QJsonObject::toVariantHash or QJsonObject::toVariantMap.

Try this;
QJsonArray params_array = jobj.value("params").toArray();
qDebug() << "params_array:: results array size = " << params_array.count();
foreach (const QJsonValue & value, a)
{
QJsonObject obj = value.toObject();
qDebug() << "obj keys " << obj.keys() ;
// here you can access your data, verify your keys with the debug a
}

Related

(Qt) Change value by key in QVariantMap nested in QVariantList (which is also nested in QVariantMap)

There is the following hierarchy:
QVariantMap <- QVariantList <- QVariantMap
The problem is QVariant::toList() and QVariant::toMap() return copies, which means I can't change value in a nested QVariantMap or QVariantList.
Is there any way to solse it?
P.S. I tried QJsonObject instead (because it's easy to convert it to a QVariantMap) but faced the same problem: I could not change QJsonObject stored in QJsonArray because operator[] for QJsonObject marked as const (and it was also problematic for me to work with QJsonValue and ULongLong together, so I returned to QVariant).
Hierarchy:
QVariantMap mainTable;
QVariantList list;
QVariantMap subTable;
subTable["id"] = 0;
list << subtable;
mainTable["list"] = list;
I've got no issues with filling it but when I tried to change stored values later (in other methods) there was the problem, because I can't change subTable["id"] value like:
mainTable["list"].toList()[index].toMap()["id"] = 12;
At first glance, I can't see other solution for your problem than this, it may be heavy...
QVariantMap mainTable;
QVariantList list;
QVariantMap subTable;
subTable["id"] = 0;
list << subTable;
mainTable["list"] = list;
qDebug() << mainTable["list"].toList()[0].toMap()["id"].toInt();
auto tempList = qvariant_cast<QVariantList>(mainTable["list"]);
auto tempSubTable = qvariant_cast<QVariantMap>(tempList[0]);
tempSubTable["id"] = 42;
tempList[0] = tempSubTable;
mainTable["list"] = tempList;
qDebug() << mainTable["list"].toList()[0].toMap()["id"].toInt();
running this code gives me
0
42

How to add object to Json file

I am trying to log data from 3 sensors to a json file. All I want to be able to accomplish is to write the Speed, Latitude and Longitude to a Json file, with an object containing each of the above. That is a json file that contains one route object, n sub objects each of which contain speed, latitude, longitude.
These 3 values I get from 3 global QList lists. Below is the json file which is stored locally. (The double values are not actual values, just for testing purposes)
{
"Sensordata": [
{
"Speed": 1,
"GPSLat":-12.5687,
"GPSLong":26.125546
},
{
"Speed": 1,
"GPSLat":-12.5687,
"GPSLong":26.125546
}
]
}
This is what the json must look like and when I add it must be formatted in the same way
void MainWindow::save_to_json() {
QFile file_obj(".../SensorData.json");
if(!file_obj.open(QIODevice::ReadOnly)){
qDebug()<<"Failed to open "<<"SensorData.json";
exit(1);
}
QTextStream file_text(&file_obj);
QString json_string;
json_string = file_text.readAll();
file_obj.close();
QByteArray data_json = json_string.toLocal8Bit();
QJsonDocument doc = QJsonDocument::fromJson(data_json);
QJsonObject rootObj = doc.object();
QJsonValue SensorData = rootObj.value("SensorData");
if(!SensorData.isArray())
{
// array expected - handle error
}
QJsonArray SensorDataArray = SensorData.toArray();
QJsonObject newObject;
newObject["Speed"] = speed_array.takeFirst();
newObject["GPSLat"] = gps_lat.takeFirst();
newObject["GPSLong"] = gps_long.takeFirst();
SensorDataArray.push_back(newObject);
}
ASSERT: "!isEmpty()" in file /home/username/Qt/5.12.1/gcc_64/include /QtCore/qlist.h, line 347
11:32:55: The program has unexpectedly finished.
11:32:55: The process was ended forcefully.
This is the error the above code creates.
To modify the data, given your example, you need to check if the contained data in the QJsonDocument is an array or a simple object. In your case, I suppose you want to append data to an array. Try something like this:
// Read the data
const QString filename = "example.json";
QJsonDocument doc = read(filename);
// Check that it's an array and append new data
QJsonValue sensorData = doc.value("SensorData");
if (!sensorData.isArray()) {
// if the doc is empty you may want to create it
}
// Get the array and insert the data
auto array = sensorData.array();
array.append(QJsonObject{
{"Speed", speed_array.takeFirst()},
{"GPSLat", gps_lat.takeFirst()},
{"GPSLong",gps_long.takeFirst(),
});
// Restore your sensor data
doc.setObject(QJsonObject{{"SensorData", array}});
// Write the new data
write(filename, doc);
A helper functions to read/write JSON documents may avoid the mistake of open/closing a file:
QJsonDocument read(const QString& filename) {
QFile file(filename);
file.open(QIODevice::ReadOnly | QIODevice::Text);
const QString val = file.readAll();
file.close();
return QJsonDocument::fromJson(val.toUtf8());
}
void write(const QString& filename, const QJsonDocument& document) {
QFile file(filename);
file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate);
file.write(document.toJson());
file.close();
}
Updates
To not overwrite the original doc, you must update the field of the root object or use QJsonValueRef.
// Get a reference to your array
QJsonObject root = doc.object();
QJsonValueRef ref = root.find("SensorData").value();
// get the array and insert the data
QJsonArray array = ref.toArray();
array.append(QJsonObject{
{"Speed", speed_array.takeFirst()},
{"GPSLat", gps_lat.takeFirst()},
{"GPSLong",gps_long.takeFirst(),
});
// Update the ref with the new data
ref = array
// update the modified data in the json document
doc.setObject(root);

Serializing/parsing multiple objects in one file in Qt C++

I need to serialize and parse multiple objects from my project, in order to save/load them when needed.
My objects will have exactly the same components : a QString name, an integer id, a QString description, and two integer x, y.
I'll need something like this :
{"name":"toto", "id":"42", "description":"tata", "x":"20", "y":"50"}
So I'll build my QJsonObject like this :
QJsonObject json;
json["id"] = object_to_serialize.get_id();
json["name"] = object_to_serialize.get_name();
json["description"] = object_to_serialize.get_description();
json["x"] = object_to_serialize.get_x();
json["y"] = object_to_serialize.get_y();
QJsonDocument filedoc(json);
file.write(filedoc.toJson);`
And in the file it will appear like this :
{"name":"toto", "id":"42", "description":"tata", "x":"20", "y":"50"}
{"name":"toto2", "id":"44", "description":"tata2", "x":"25", "y":"547"}
{"name":"toto3", "id":"46", "description":"tata3", "x":"21", "y":"580"}
My serialiser will take in parameter the object, the savefile name, and transform the object into a QJsonObject. It will need then to read the file to check if an object with the same id is here. If it is here, it will need to replace it, and if it is not, it will append it.
I'm a little lost between my serialization options and how to read it ;
Should I make a QJsonArray with multiple QJsonObject inside or QJsonObject with QJsonArrays ?
When I read it, I will need to check for the id ; but will a
foreach(object.value["id"] == 42)
//create the QJsonObject from the one with 42 and change it with the new data
will do to parse the object and not all of them ? Is there a better way ?
Thank you in advance for your answers.
You can have an array of json object, each of them having an ID so you can parse the relevant ones.
Although you could also parse all of them and add them in a map, as long as you don't have very heavy files it should be fine.
void parseJson(const QString &data)
{
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8());
if (doc.isNull())
{
war("invalid json document");
return;
}
QJsonArray jsonArray = doc.array();
foreach (const QJsonValue & value, jsonArray) {
QJsonObject obj = value.toObject();
if (obj.contains("id"))
{
if (obj["id"].toInt() == yourId) parseObject(obj);
}
}
}
void parseObject(const QJsonObject &obj)
{
if (obj.contains("valueA")) valueA = obj["valueA"].toDouble();
if (obj.contains("valueB")) valueB = obj["valueB"].toDouble();
}
This will work just fine if your file is not too big
Bigger Files
Now if you have very large file, it might be an issue to load it all in memory and parse it.
Since your structure is always the same and quite simple, JSON might not be the best choice, one more efficient method would be to do your own parser (or use probably some existing ones) that could read the file and process it as a stream.
Another method, would be to have one JSON entry per line preceded by an ID with a fixed number of digit. Load this in a QHash lookup and then only read id of interest from the file and only parse a small section.
// This code is not tested and is just to show the principle.
#define IDSIZE 5
QHash<int64, int64> m_lookup; // has to be global var
// For very large file, this might take some time and can be done on a separate thread.
// it needs to be done only once at startup (given the file is not modified externally)
void createLookup(const QString &fileName)
{
QFile inputFile(fileName);
if (inputFile.open(QIODevice::ReadOnly))
{
QTextStream in(&inputFile);
while (!in.atEnd())
{
int position = in.pos(); // store the position in the file
QString line = in.readLine();
int id = line.mid(0,IDSIZE).toInt(); // 5 digit id (like 00001, 00002, etc...
m_lookup[id] = position + IDSIZE;
}
inputFile.close();
}
}
QString getEntry(const QString &fileName, int64 id)
{
if (m_lookup.contains(id))
{
QFile inputFile(fileName);
if (inputFile.open(QIODevice::ReadOnly))
{
inputFile.seek(m_lookup[id]);
QString data = inputFile.readLine();
inputFile.close();
return data;
} else {
return QString(); // or handle error
}
} else {
return QString(); // or handle error
}
}
// use example
QString data = getEntry(id);
if (data.length() > 0)
{
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8());
if (!doc.isNull())
{
// assign your variables
}
}
and your data file looking like this:
00042{"name":"toto", "id":"42", "description":"tata", "x":"20", "y":"50"}
00044{"name":"toto2", "id":"44", "description":"tata2", "x":"25", "y":"547"}
00046{"name":"toto3", "id":"46", "description":"tata3", "x":"21", "y":"580"}
The advantage of this method, it will only read the entry of interest, and avoid having to load MB or GB of data in memory just to get a specific entry.
This could further be improved with a lookup table stored at the beginning of the file.

Why can't I parse the Cryptsy JSON API with Qt5/C++?

I'm trying to parse this JSON Web-API using Qt5 and C++ using QJsonDocument and QJsonObject as seen here. But I fail to access the JSON value of the QJsonObject.
This is what I've tried so far:
// Contains the whole API as QString...
QString data = QString(reply->readAll());
// Reads the JSON as QJsonDocument...
QJsonDocument jsonResponse = QJsonDocument::fromJson(data.toUtf8());
// Reads the JSON as QJsonObject...
QJsonObject jsonObject = jsonResponse.object();
Now I have my object well prepared, but trying to access the values of the JSON somehow fails:
// This returns an empty string ""!?!
qDebug() << jsonObject.value("success").toString();
Well, maybe I got the keys wrong:
// Let's check the keys...
QStringList stringList = jsonObject.keys();
for (QStringList::Iterator it = stringList.begin(); it != stringList.end(); ++it)
{
// This returns "success" and "return" - huh!?!
qDebug() << *it;
}
OK, the keys are veryfied, why is it not working?
// Let's check the values by using the keys directly...
for (QStringList::Iterator it = stringList.begin(); it != stringList.end(); ++it)
{
// This returns empty strings "" and "" - now what?!?
qDebug() << jsonObject.value(*it).toString();
}
This again, makes no sense at all. I can't see why I can not access the value of the JSON object by the keys. Any idea?
I tried exactly the same code on other JSON APIs (for example this one) without any issues. I am totally stuck here.
Here's my solution for Qt5 Json parsing the Cryptsy API.
QEventLoop loopEvent;
QNetworkAccessManager namMNGR;
QObject::connect(&namMNGR, SIGNAL(finished(QNetworkReply*)), &loopEvent, SLOT(quit()));
QNetworkRequest req(QUrl(QString("http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=%1").arg(marketID)));
QNetworkReply *reply = namMNGR.get(req);
loopEvent.exec();
//Json API parsing begins.
QString jsonSTR = reply->readAll();
if (!(reply->error() == QNetworkReply::NoError)) {
delete reply; //API Connection Problem.
}
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonSTR.toUtf8());
QJsonObject obj1 = jsonDocument.object();
QJsonValue val1 = obj1.value(obj1.keys().first());
QJsonObject obj2 = val1.toObject();
QJsonValue val2 = obj2.value(obj2.keys().first());
QJsonObject obj3 = val2.toObject();
QJsonValue marketDataValue = obj3.value(obj3.keys().first());
QJsonObject marketDataObject = marketDataValue.toObject();
QJsonArray sellordersArray = marketDataObject["sellorders"].toArray();
Have you managed to get Authenticated POST API data from Qt5? I'm trying to figure out how to do it.

Creating a QVariantMap in Qt with strings

Apologies for asking something this trivial but I just can't seem to get it right so I guess I've completely misunderstood everything I thought I knew about memory management.
I have a function that parses a network reply for some data and it looks like this:
// Call from another function
QVariantMap *mappedResult = handleReply(reply);
...
// Function
QVariantMap *handleReply(QNetworkReply *reply) {
QVariantMap *result = new QVariantMap;
QVariant testvalue = new QVariant("testvalue");
result->insert("testkey", testvalue);
if (reply->error() > 0) {
qDebug() << "Error number = " + reply->errorString();
QVariant variant = QVariant.fromValue(reply->errorString());
result->insert("error", variant);
} else {
QJsonDocument jsonResponse = QJsonDocument::fromJson(jsonString.toUtf8());
QJsonObject jsonResponseObject = jsonResponse.object();
*result = jsonResponseObject.toVariantMap();
}
return result;
}
If there is no error, the result is parsed fine by the built in toVariantMap function. When there is an error however, I would like to create an entry in the result that is sent back from the function. As you can see in the function, I'm trying to create QVariants from Strings in two different ways.
The first approach gives an error like this:
C:\Qt\5.2.0\mingw48_32\include\QtCore\qvariant.h:466: error: 'QVariant::QVariant(void*)' is private inline QVariant(void *) Q_DECL_EQ_DELETE;
^
The second approach like this:
[PATH] error: expected primary-expression before '.' token QVariant variant = QVariant.fromValue(reply->errorString());
I've also tried setting the values like this:
result->insert("testkey", "testvalue");
result->insert("error", reply->errorString());
The last approach doesn't give compile errors but the result variable in the return statement cannot be inspected in the debugger and as soon as the return statement is executed the application crashes with memory problems indicating I'm not using "new" properly.
"Error accessing memory address 0x...".
If someone with at least a little knowledge could help me sort out how to perform this simple task, I would be very greatful. How can I return a QVariantMap from my function with custom strings as key/value pairs?
Cheers.
I would write your function in the following way (with fixes):
QVariantMap *handleReply(QNetworkReply *reply) {
QVariantMap *result = new QVariantMap;
QVariant testvalue("testvalue"); // <- fixed here
result->insert("testkey", testvalue);
if (reply->error() > 0) {
qDebug() << "Error number = " + reply->errorString();
QVariant variant(reply->errorString()); // <- fixed here
result->insert("error", variant);
} else {
QJsonDocument jsonResponse = QJsonDocument::fromJson(jsonString.toUtf8());
QJsonObject jsonResponseObject = jsonResponse.object();
*result = jsonResponseObject.toVariantMap();
}
return result;
}
However it is still unclear, why do you need to work with a pointer to the variant map instread of passing it by value?