How to extract value from json array with QJsonDocument format - c++

I'm getting a json format like this and I want to get the value of "Duration", "Id", "LoadCumulLimit" and "Notes".
QJsonDocument({"d":{"results":[{"Duration":"420.000","Id":"123456789XYZ","LoadCumulLimit":"15.000","NavWpNioshToOpNoish":{"__deferred":{"uri":"http://xxx/WorkplaceNOISHDataSet('123456789XYZ')/NavWpNioshToOpNoish"}},"Notes":"123456789XYZ","__metadata":{"id":"xxx/WorkplaceNOISHDataSet('123456789XYZ')","type":"xxx.WorkplaceNOISHData","uri":"xxx/WorkplaceNOISHDataSet('123456789XYZ')"}}]}})
I tried to do this but it doesn't work and it return empty with array
`
QJsonDocument document = QJsonDocument::fromJson(content.toUtf8());
QJsonArray documentArray = document.array();
QStringList wordList;
for (const QJsonValue &i : documentArray)
{
//qInfo() << i.toString() << endl;
wordList << i.toString();
}
Could you guys give me a help or any suggest?

You could convert the QJsonDocument to a QVariant. Then you can use QVariantMap or QVariantList to walk the document and use the appropriate toString() or toDouble() to retrieve the values.
The following is hard-coded to your JSON there are only minimal validation checks included. (i.e. it is a disclaimer that the code is presented for educational purposes only and made not be production ready).
bool parse()
{
QString json = "{\"d\":{\"results\":[{\"Duration\":\"420.000\",\"Id\":\"123456789XYZ\",\"LoadCumulLimit\":\"15.000\",\"NavWpNioshToOpNoish\":{\"__deferred\":{\"uri\":\"http://xxx/WorkplaceNOISHDataSet('123456789XYZ')/NavWpNioshToOpNoish\"}},\"Notes\":\"123456789XYZ\",\"__metadata\":{\"id\":\"xxx/WorkplaceNOISHDataSet('123456789XYZ')\",\"type\":\"xxx.WorkplaceNOISHData\",\"uri\":\"xxx/WorkplaceNOISHDataSet('123456789XYZ')\"}}]}}";
QJsonDocument document = QJsonDocument::fromJson(json.toUtf8());
if (document.isEmpty() || document.isNull()) return false;
QVariantMap root = document.toVariant().toMap();
if (root.isEmpty()) return false;
QVariantMap d = root["d"].toMap();
if (d.isEmpty()) return false;
QVariantList results = d["results"].toList();
if (results.isEmpty()) return false;
foreach (QVariant varResult, results)
{
QVariantMap result = varResult.toMap();
if (result.isEmpty()) return false;
bool ok = true;
double duration = result["Duration"].toDouble(&ok);
if (!ok) return false;
QString id = result["Id"].toString();
if (id.isEmpty() || id.isNull()) return false;
double loadCumulLimit = result["LoadCumulLimit"].toDouble(&ok);
if (!ok) return false;
QString notes = result["Notes"].toString();
if (!notes.isEmpty() || notes.isNull()) return false;
qDebug() << id << duration << loadCumulLimit << notes; // "123456789XYZ" 420 15 "123456789XYZ"
}
return true;
}
Alternatively, you can just use QJsonDocument, QJsonValue and QJsonArray to walk the document and use the corresponding toString() and toDouble() to retrieve the values. Again, there are minimal validation checks included:
bool parse2()
{
QString json = "{\"d\":{\"results\":[{\"Duration\":\"420.000\",\"Id\":\"123456789XYZ\",\"LoadCumulLimit\":\"15.000\",\"NavWpNioshToOpNoish\":{\"__deferred\":{\"uri\":\"http://xxx/WorkplaceNOISHDataSet('123456789XYZ')/NavWpNioshToOpNoish\"}},\"Notes\":\"123456789XYZ\",\"__metadata\":{\"id\":\"xxx/WorkplaceNOISHDataSet('123456789XYZ')\",\"type\":\"xxx.WorkplaceNOISHData\",\"uri\":\"xxx/WorkplaceNOISHDataSet('123456789XYZ')\"}}]}}";
QJsonDocument document = QJsonDocument::fromJson(json.toUtf8());
if (document.isEmpty() || document.isNull()) return false;
QJsonValue d = document["d"];
if (d.isNull() || d.isUndefined()) return false;
QJsonArray results = d["results"].toArray();
if (results.isEmpty()) return false;
foreach (QJsonValue result, results)
{
double duration = result["Duration"].toDouble();
QString id = result["Id"].toString();
if (id.isEmpty() || id.isNull()) return false;
double loadCumulLimit = result["LoadCumulLimit"].toDouble();
QString notes = result["Notes"].toString();
if (!notes.isEmpty() || notes.isNull()) return false;
qDebug() << id << duration << loadCumulLimit << notes; // "123456789XYZ" 420 15 "123456789XYZ"
}
return true;
}

You have:
object d {
object results {
[ { several objects to be extracted} ]
}
}
To extract a value of an object by given a key, call operator[](key) on QJsonValue.
When you have an array, to extract its first item call operator[](0) on this array. When you have found an object at desired key, you can convert its value to the value of specified type by toString/toInt/toDouble etc. methods of QJsonValue.
Short version:
QJsonValue item0 = document["d"]["results"].toArray()[0];
QStringList wordList;
wordList << item0["Duration"].toString() << item0["Id"].toString() << item0["LoadCumulLimit"].toString() << item0["Notes"].toString();
the longer version:
QJsonValue dObj = document["d"];
QJsonValue resultsObj = dObj["results"];
QJsonArray resultsArray = resultsObj.toArray();
QJsonValue item0 = resultsArray[0];
QStringList wordList;
wordList << item0["Duration"].toString() << item0["Id"].toString() << item0["LoadCumulLimit"].toString() << item0["Notes"].toString();

Related

c++ qt can i fill a QVariant with multiple struct objects

I have the following struct:
struct FileInfo {
QString fileName;
QString fileSize;
QString md5Sum;
};
is it possible to put approximately 30 such objects into a QVariant and then be able to iterate through the QVariant to retrieve one of the structs based on index and then cast the object back into a struct and query the struct for say fileSize of a specific file? Is there a better approach to my issue?
You can consider QVariantList to store object as QVariant. Of course, you can convert back into your custom struct.
Example.
struct FileInfo {
QString fileName;
QString fileSize;
QString md5sum;
};
int main()
{
QVariantList variantList;
for(int i = 0; i < 30; i++) {
FileInfo info{QString("File %1").arg(i), QString("size %1").arg(i), QString("md5sum %1").arg(i)};
QVariant var;
var.setValue(info);
variantList.append(var);
}
for (auto v: variantList) {
FileInfo f = v.value<FileInfo>();
qDebug() << "File name: " << f.fileName << "\tfile size: " << f.fileSize;
}
return 0;
}

Is there a way find a key in the whole json file using QJsonObject?

A key in any level of the json hierarchy, how can I find that key without knowing exact keys in the path?
Generally, this can be solved with a recursive function (a function which calls itself). We first pass it the document's object, then check the object's keys. If no keys are found, we will apply the same function on the values of each key. If an array is passed, we should iterate through it.
QJsonValue findKey(const QString& key, const QJsonValue& value) {
if (value.isObject()) {
const QJsonObject obj = value.toObject();
if (obj.contains(key))
return obj.value(key); // return 'early' if object contains key
for (const auto& value : obj) {
QJsonValue recurse = findKey(key, value); // call itself, forwarding a value
if (!recurse.isNull())
return recurse; // value found, return 'early'
}
} else if (value.isArray()) {
for (const auto& value : value.toArray()) {
QJsonValue recurse = findKey(key, value);
if (!recurse.isNull())
return recurse;
}
}
return QJsonValue(); // base case: a null value
}
int main(int argc, char *argv[])
{
QFile file(":/res/scratch.json"); // json stored in a qrc with /res/ prefix
file.open(QIODevice::ReadOnly);
if (!file.isOpen()) {
qDebug() << "error: couldn't open scratch.json";
return 0;
}
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
qDebug() << "value:" << findKey("treasure", doc.object());
}
An example of a JSON file and relevant output:
scratch.json:
{
"deck": [
"first mate",
"sailor",
"john muir"
],
"cabin": [
{
"name": "lamp"
},
{
"name": "treasure chest",
"items": {
"diamonds": 3,
"silver": 5,
"gold": 10,
"safebox": {
"treasure": "shiny"
}
}
}
]
}
Output:
value: QJsonValue(string, "shiny")

How to get QJsonObject values in order?

I want to use Json in Qt and my data inside the QJsonObject must be in some particular order since we have some confirmation method via a token and getting a hash of the Json.
Since QJsonObject sorts its key-value pairs in alphabetical order I something like this to happen:
QJsonObject object;
object.insert("B","b");
object.insert("A","a");
qDebug() << QJsonDocument(object).toJson(); // this-line
I want this-line to printout something like this:
{"B":"b","A":"a"}
but instead i get this:
{
"A":"a",
"B":"b"
}
I have written a class:
jsonobject.h
class JsonObject
{
public:
JsonObject();
void insert(QString key,QJsonValue value);
void remove(QString key);
QString toJsonString();
QString convertQJsonValue2String(QJsonValue value);
QString convertQJsonArray2String(QJsonArray array);
QString convertQJsonObject2String(QJsonObject object);
private:
void appendKey(QString key,QString * out);
QJsonObject qJsonObject;
QStringList keysInOrder;
};
jsonobject.cpp
#include "jsonobject.h"
JsonObject::JsonObject()
{
keysInOrder.clear();
}
void JsonObject::insert(QString key, QJsonValue value)
{
if(!keysInOrder.contains(key))
keysInOrder.append(key);
qJsonObject.insert(key,value);
}
void JsonObject::remove(QString key)
{
keysInOrder.removeOne(key);
qJsonObject.remove(key);
}
QString JsonObject::toJsonString()
{
QString out;
for(int i = 0 ; i < keysInOrder.size() ; i++)
{
appendKey(keysInOrder[i],&out);
out.append(convertQJsonValue2String(qJsonObject.value(keysInOrder[i])));
if(i != (keysInOrder.size() - 1))
out.append(",");
}
return out.prepend("{").append("}");
}
QString JsonObject::convertQJsonObject2String(QJsonObject object)
{
QStringList keys = object.keys();
QString out;
for(int i = 0 ; i < keys.size() ; i++)
{
appendKey(keys[i],&out);
out.append(convertQJsonValue2String(object[keys[i]]));
if(i != keys.size() - 1)
out.append(",");
}
return out.prepend("{").append("}");
}
void JsonObject::appendKey(QString key, QString *out)
{
out->append("\"");
out->append(key);
out->append("\"");
out->append(":");
}
QString JsonObject::convertQJsonValue2String(QJsonValue value)
{
switch(value.type())
{
case QJsonValue::Null:
return QString("null");
break;
case QJsonValue::Bool:
return QString("%1").arg(value.toBool());
break;
case QJsonValue::Double:
return QString("%1").arg(value.toDouble());
break;
case QJsonValue::String:
return value.toString().prepend("\"").append("\"");
break;
case QJsonValue::Object:
return convertQJsonObject2String(value.toObject());
break;
case QJsonValue::Array:
return convertQJsonArray2String(value.toArray());
break;
case QJsonValue::Undefined:
return QString();
break;
}
return QString();
}
QString JsonObject::convertQJsonArray2String(QJsonArray array)
{
QString out;
for(int i = 0 ; i < array.size() ; i++)
{
QJsonValue value = array.at(i);
out.append(convertQJsonValue2String(value));
if(i != (array.size() - 1))
out.append(",");
}
return out.prepend("[").append("]");
}
the problem here is that I have QJsonObjects inside and my converQJSonObject2String(QJsonObject object) still don't have that order.
To me this looks like you're trying to use json for something it is not meant to do. Json does not have a canonical representation, so even if you manage to order the keys correctly, you might still run into problems. (Keys with non-ascii characters, whitespace differences, ...)
While it is certainly possible to do what you're looking for (I suggest using another library that offers this possibility), try to take a step back and think about what you're trying to achieve.
Would it be possible to take the hash of the json you receive from the server directly?

no match for 'operator[]' (operand types are 'QVariant' and 'const char [2]') QVariant/QVariantMap

Im trying to make the start of a wrapper class for json in qt 5.1 and i'm working on a function which which will check if the var inputted is a QVariantMap or just a QVariant and everything works well till i go the second level of the muli dimen array. here is my array structure and class code.
JsonHelper jh;
QVariantMap obj = jh.getJsonObjectVarientMap(data);
This causes me the problems, when i just use "obj" or "obj["1"]" there is no issues, only when i
// obj["4"]["3"] this causes the problems
qDebug() << "Your returned val is : " << jh.keySearchVal(obj["4"]["3"],arr_index_txt);
QMap<QString,QVariant> mp = obj["4"].toMap();
foreach(QString key,mp.keys())
{
// this works ok
qDebug() << "key : " << key << " : val : " << mp[key];
}
QVariantMap JsonHelper::getJsonObjectVarientMap(QString in)
{
QJsonDocument d = QJsonDocument::fromJson(in.toUtf8());
return d.object().toVariantMap();
}
QVariant JsonHelper::keySearchVal(QVariant source, QString key)
{
QString type(source.typeName());
if(type=="QVariantMap")
{
QMap<QString, QVariant> map = source.toMap();
foreach(QString key_inner,map.keys())
{
QVariant in = map[key_inner];
if(key_inner==key)
{
return getVariantVal(in);
}
}
}
return "";
}
QVariant JsonHelper::keySearchVal(QVariantMap source, QString key)
{
foreach(QString key_inner,source.keys())
{
if(key_inner==key)
{
return source[key_inner];
}
}
return "";
}
QVariant JsonHelper::getVariantVal(QVariant in)
{
qDebug() << "from variant";
QString type(in.typeName());
if(type=="QVariantMap")
{
return in.toMap();
}
return in;
}
// obj["4"]["3"] this causes the problems
That is invalid because QVariant does not have an operator[] overload. That is also what the compiler is trying to tell you with this:
no match for 'operator[]' (operand types are 'QVariant' and 'const char [2]') QVariant/QVariantMap
You will need to convert any nested QVariant explicitly to QVariantMap if that is the underlying data type. See the following method for details:
QMap QVariant::toMap() const
Returns the variant as a QMap if the variant has type() QMetaType::QVariantMap; otherwise returns an empty map.
It is not the main point, but you also have two further issues:
You seem to use the word Varient as opposed to Variant.
Your code lacks error checking and reporting for conversions, etc.

MongoDB C++ BSONObj lifetimes

When using the C++ client library and retain an BSONObj object returned from a cursor->next(), will a subsequent new query (using the same connection) somehow destroy that object ? My understanding is that its a smart pointer and will survive until the last reference to it goes out of scope. Practice however shows me that a subsequent query seemingly destroys the object. I get a segmentation fault when I access hasField/getField methods of the object after the new query is traversed.
Following is the relevant code:
BSONObj DB::getmsg
(
const string &oid
)
{
BSONObj query = BSON("_id" << OID(oid));
auto_ptr<DBClientCursor> cursor = c.query("nsdb.messages", query);
if (cursor->more())
return cursor->next();
throw Exception("could not find object %s", oid.c_str());
}
void DB::getchildren
(
const string &oid,
vector<BSONObj> &children
)
{
BSONObj query = BSON("parent" << oid);
BSONObj fields = BSON("_id" << 1);
auto_ptr<DBClientCursor> cursor =
c.query("nsdb.messages", query, 0, 0, &fields);
while (cursor->more())
children.push_back(cursor->next());
}
void DB::message
(
const string &oid,
string &str,
bool body
)
{
connect();
BSONObjBuilder response;
try
{
BSONObj n = getmsg(oid);
response.append("message", n);
vector<BSONObj> children;
getchildren(oid, children);
if (children.size() > 0)
{
BSONArrayBuilder a;
vector<BSONObj>::iterator i;
for (i = children.begin(); i != children.end(); i++)
{
if (!(*i).isEmpty() && (*i).hasField("_id"))
{
string child = (*i).getField("_id").__oid().str();
a.append("http://127.0.0.1:12356/lts/message/" + child);
}
}
if (a.len() > 0)
response.append("children", a.done());
}
if (body)
{
string fname;
if (n.hasField("fname"))
fname = n.getField("fname").str();
if (fname.empty())
{
throw Exception("no fname field in message record %s",
n.jsonString().c_str());
}
auto_mmapR<const void *, int> m(fname);
response.appendBinData("body", m.size(), BinDataGeneral,
m.get());
}
str = response.obj().jsonString();
}
catch (const DBException &x)
{
throw Exception("%s", x.what());
}
}
If I move the part:
string fname;
if (n.hasField("fname"))
fname = n.getField("fname").str();
to a spot before getchildren(...) is called, then the fname field is correctly retrieved from the message otherwise the exception is thrown 'no fname field in message record'
Okay, I did not understood too much of your code, but short answer:
- The data returned by the cursor is invalidated at the next iteration or when the cursor is destroyed.
Use the "getOwned()" to have a copy of the BSONObj object:
cursor->next().getOwned();