Parse nested JSON with QJsonDocument in Qt - c++

I am interested in seeing how we can use Qt's QJsonDocument to parse all entries from a simple nested JSON (as I have just started studying this).
nested json example:
{
"city": "London",
"time": "16:42",
"unit_data":
[
{
"unit_data_id": "ABC123",
"unit_data_number": "21"
},
{
"unit_data_id": "DEF456",
"unit_data_number": "12"
}
]
}
I can parse the non-nested parts of it like so:
QJsonObject jObj;
QString city = jObj["city"].toString();
QString time = jObj["time"].toString();

I am not sure what you are asking, but perhaps this might help:
QJsonDocument doc;
doc = QJsonDocument::fromJson("{ "
" \"city\": \"London\", "
" \"time\": \"16:42\", "
" \"unit_data\": "
" [ "
" { "
" \"unit_data_id\": \"ABC123\", "
" \"unit_data_number\": \"21\" "
" }, "
" { "
" \"unit_data_id\": \"DEF456\", "
" \"unit_data_number\": \"12\" "
" } "
" ] "
" }");
// This part you have covered
QJsonObject jObj = doc.object();
qDebug() << "city" << jObj["city"].toString();
qDebug() << "time" << jObj["time"].toString();
// Since unit_data is an array, you need to get it as such
QJsonArray array = jObj["unit_data"].toArray();
// Then you can manually access the elements in the array
QJsonObject ar1 = array.at(0).toObject();
qDebug() << "" << ar1["unit_data_id"].toString();
// Or you can loop over the items in the array
int idx = 0;
for(const QJsonValue& val: array) {
QJsonObject loopObj = val.toObject();
qDebug() << "[" << idx << "] unit_data_id : " << loopObj["unit_data_id"].toString();
qDebug() << "[" << idx << "] unit_data_number: " << loopObj["unit_data_number"].toString();
++idx;
}
The output I get is:
city "London"
time "16:42"
"ABC123"
[ 0 ] unit_data_id : "ABC123"
[ 0 ] unit_data_number: "21"
[ 1 ] unit_data_id : "DEF456"
[ 1 ] unit_data_number: "12"

In JSON notation, everything should be formatted in key-value. Keys are always strings, but values could be string literals ("example"), number literals , arrays ([]) and objects ({}).
QJsonDocument::fromJson(...).object() returns the root object of a given JSON string. Recall that objects are written by {} notation. This method gives you a QJsonObject. This JSON object has 3 keys ("city", "name" and "unit_data") which value of these keys are of type string literal, string literal and array respectively.
So if you want to access the data stored in that array you should do:
QJsonArray array = rootObj["unit_data"].toArray();
Note that arrays don't have keys, they have only values which could be of the three types mentioned above. In this case, the array holds 2 objects which can be treated as other JSON objects. So,
QJsonObject obj = array.at(0).toObject();
Now the obj object points to the following object:
{
"unit_data_id": "ABC123",
"unit_data_number": "21"
}
So, you should now be able to do what you want. :)

It can happen that one of the elements inside your JSON has more elements inside. It can also happen that you don't know the characteristics of the file (or you want to have a general function).
Therefore you can use a function for any JSON:
void traversJson(QJsonObject json_obj){
foreach(const QString& key, json_obj.keys()) {
QJsonValue value = json_obj.value(key);
if(!value.isObject() ){
qDebug() << "Key = " << key << ", Value = " << value;
}
else{
qDebug() << "Nested Key = " << key;
traversJson(value.toObject());
}
}
};

Related

QJsonDocument::fromJson cannot parse QJsonDocument::toJson QByteArray

I'm using QJsonDocument::toJson to convert a QJsonObject into a QByteArray, and somewhere else in the program, I read that QByteArray and convert it to a QJsonObject using QJsonDocument::fromJson. It doesn't work and I don't know why. QJsonParseError doesn't show anything.
How can I successfully convert to and from QByteArray <--> QJsonObject?
If I run:
QJsonDocument doc, doc2;
QJsonParseError jsonerror;
QJsonObject original, copy;
original.insert("foo", 1);
original.insert("bar",2);
doc.setObject(original);
doc2.fromJson(doc.toJson(), &jsonerror);
copy = doc2.object();
qDebug() << doc.toJson();
qDebug() << jsonerror.errorString();
qDebug() << "Null: " << doc2.isNull() << " Object: " << doc2.isObject() << " Array: " << doc2.isArray() << " Empty: " << doc2.isEmpty();
qDebug() << copy.size();
I get the following output:
"{\n "bar": 2,\n "foo": 1\n}\n"
"no error occurred"
Null: true Object: false Array: false Empty: true
0
I expected the object copy to contain the key-value pairs from original. It seems like fromJson is not managing to read the result of toJson.
I also tried using the format identifier in toJson to force it to be compact doc2.fromJson(doc.toJson(QJsonDocument::Compact), &jsonerror); and while the output is different, the resulting object is still empty
"{"bar":2,"foo":1}"
"no error occurred"
Null: true Object: false Array: false Empty: true
0
The method fromJson is a static member function, so calling doc2.fromJson(...) does not change anything inside doc2, instead it constructs a new QJsonDocument and as it has nowhere to go (it is not assigned to anything) it is lost.
This is the example above corrected:
QJsonDocument doc;
QJsonParseError jsonerror;
QJsonObject original, copy;
original.insert("foo", 1);
original.insert("bar",2);
doc.setObject(original);
QJsonDocument doc2 = QJsonDocument::fromJson(doc.toJson(), &jsonerror);
copy = doc2.object();
qDebug() << doc.toJson();
qDebug() << jsonerror.errorString();
qDebug() << "Null: " << doc2.isNull() << " Object: " << doc2.isObject() << " Array: " << doc2.isArray() << " Empty: " << doc2.isEmpty();
qDebug() << copy.size();
Which generates the output:
"{\n "bar": 2,\n "foo": 1\n}\n"
"no error occurred"
Null: false Object: true Array: false Empty: false
2

Reading Items inside of an array in an array C++

I have a very quick question...
I'm using nlohmann's json library.
My issue is that when I go to print a element, the program stops responding.
My json file
{
"Consoleprinting": false,
"Input" : [{"Code" : [{"Name": "EC", "Keybind": "VK_NUMPAD1"}] }]
}
What I have tried.
nlohmann::json jsonData = nlohmann::json::parse(i_Read);
std::cout << jsonData << std::endl;
for (auto& array : jsonData["Input"]) {
std::cout << array["Code"] << std::endl;
}
^ This works but it prints out
[{"Name": "EC", "Keybind": "VK_NUMPAD1"}]
How can I get this it print out just the name?
array["Code"] is an array containing a single collection of key-value pairs, so you need to write:
std::cout << array["Code"][0]["Name"] << std::endl;

Query nested BSON documents with mongo c++ driver

I have a bsoncxx::document::view bsonObjViewand a std::vector<std::string> path that represents keys to the value we are searching for in the BSON document (first key is top level, second key is depth 1, third key depth 2, etc).
I'm trying to write a function that given a path will search the bson document:
bsoncxx::document::element deepFieldAccess(bsoncxx::document::view bsonObj, const std::vector<std::string>& path) {
assert (!path.empty());
// for each key, find the corresponding value at the current depth, next keys will search the value (document) we found at the current depth
for (auto const& currKey: path) {
// get value for currKey
bsonObj = bsonObj.find(currKey);
}
// for every key in the path we found a value at the appropriate level, we return the final value we found with the final key
return bsonObj;
}
How to make the function work? What type should bsonObjbe to allow for such searches within a loop? Also, how to check if a value for currKey has been found?
Also, is there some bsoncxx built in way to do this?
Here is an example json document followed by some paths that point to values inside of it. The final solution should return the corresponding value when given the path:
{
"shopper": {
"Id": "4973860941232342",
"Context": {
"CollapseOrderItems": false,
"IsTest": false
}
},
"SelfIdentifiersData": {
"SelfIdentifierData": [
{
"SelfIdentifierType": {
"SelfIdentifierType": "111"
}
},
{
"SelfIdentifierType": {
"SelfIdentifierType": "2222"
}
}
]
}
}
Example paths:
The path [ shopper -> Id -> targetValue ] points to the string "4973860941232342".
The path [ SelfIdentifiersData -> SelfIdentifierData -> array_idx: 0 -> targetValue ] points to the object { "SelfIdentifierType": { "SelfIdentifierType": "111" } }.
The path [ SelfIdentifiersData -> SelfIdentifierData -> array_idx: 0 -> SelfIdentifierType -> targetValue ] points to the object { "SelfIdentifierType": "111" }.
The path [ SelfIdentifiersData -> SelfIdentifierData -> array_idx: 0 -> SelfIdentifierType -> SelfIdentifierType -> targetValue ] points to the string "111".
Note that the paths are of the type std::vector<std::string> path. So the final solution should return the value that the path points to. It should work for arbitrary depths, and also for paths that point TO array elements (second example path) and THROUGH array elements (last 2 example paths). We assume that the key for an array element at index i is "i".
Update: Currently, the approach suggested by #acm fails for paths with array indices (paths without array indices work fine). Here is all the code to reproduce the issue:
#include <iostream>
#include <bsoncxx/json.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
std::string turnQueryResultIntoString3(bsoncxx::document::element queryResult) {
// check if no result for this query was found
if (!queryResult) {
return "[NO QUERY RESULT]";
}
// hax
bsoncxx::builder::basic::document basic_builder{};
basic_builder.append(bsoncxx::builder::basic::kvp("Your Query Result is the following value ", queryResult.get_value()));
std::string rawResult = bsoncxx::to_json(basic_builder.view());
std::string frontPartRemoved = rawResult.substr(rawResult.find(":") + 2);
std::string backPartRemoved = frontPartRemoved.substr(0, frontPartRemoved.size() - 2);
return backPartRemoved;
}
// TODO this currently fails for paths with array indices
bsoncxx::document::element deepFieldAccess3(bsoncxx::document::view bsonObj, const std::vector<std::string>& path) {
if (path.empty())
return {};
auto keysIter = path.begin();
const auto keysEnd = path.end();
std::string currKey = *keysIter; // for debug purposes
std::cout << "Current key: " << currKey;
auto currElement = bsonObj[*(keysIter++)];
std::string currElementAsString = turnQueryResultIntoString3(currElement); // for debug purposes
std::cout << " Query result for this key: " << currElementAsString << std::endl;
while (currElement && (keysIter != keysEnd)) {
currKey = *keysIter;
std::cout << "Current key: " << currKey;
currElement = currElement[*(keysIter++)];
currElementAsString = turnQueryResultIntoString3(currElement);
std::cout << " Query result for this key: " << currElementAsString << std::endl;
}
return currElement;
}
// execute this function to see that queries with array indices fail
void reproduceIssue() {
std::string testJson = "{\n"
" \"shopper\": {\n"
" \"Id\": \"4973860941232342\",\n"
" \"Context\": {\n"
" \"CollapseOrderItems\": false,\n"
" \"IsTest\": false\n"
" }\n"
" },\n"
" \"SelfIdentifiersData\": {\n"
" \"SelfIdentifierData\": [\n"
" {\n"
" \"SelfIdentifierType\": {\n"
" \"SelfIdentifierType\": \"111\"\n"
" }\n"
" },\n"
" {\n"
" \"SelfIdentifierType\": {\n"
" \"SelfIdentifierType\": \"2222\"\n"
" }\n"
" }\n"
" ]\n"
" }\n"
"}";
// create bson object
bsoncxx::document::value bsonObj = bsoncxx::from_json(testJson);
bsoncxx::document::view bsonObjView = bsonObj.view();
// example query which contains an array index, this fails. Expected query result is "111"
std::vector<std::string> currQuery = {"SelfIdentifiersData", "SelfIdentifierData", "0", "SelfIdentifierType", "SelfIdentifierType"};
// an example query without array indices, this works. Expected query result is "false"
//std::vector<std::string> currQuery = {"shopper", "Context", "CollapseOrderItems"};
bsoncxx::document::element queryResult = deepFieldAccess3(bsonObjView, currQuery);
std::cout << "\n\nGiven query and its result: [ ";
for (auto i: currQuery)
std::cout << i << ' ';
std::cout << "] -> " << turnQueryResultIntoString3(queryResult) << std::endl;
}
There is not a built-in way to to do this, so you will need to write a helper function like the one you outline above.
I believe the issue you are encountering is that the argument to the function is a bsoncxx::document::view, but the return value of view::find is a bsoncxx::document::element. So you need to account for the change of type somewhere in the loop.
I think I would write the function this way:
bsoncxx::document::element deepFieldAccess(bsoncxx::document::view bsonObj, const std::vector<std::string>& path) {
if (path.empty())
return {};
auto keysIter = path.begin();
const auto keysEnd = path.end();
auto currElement = bsonObj[*(keysIter++)];
while (currElement && (keysIter != keysEnd))
currElement = currElement[*(keysIter++)];
return currElement;
}
Note that this will return an invalid bsoncxx::document::element if any part of the path is not found, or if the path attempts to traverse into an object that is not a actually a BSON document or BSON array.

accessing elements from nlohmann json

My JSON file resembles this
{
"active" : false,
"list1" : ["A", "B", "C"],
"objList" : [
{
"key1" : "value1",
"key2" : [ 0, 1 ]
}
]
}
Using nlohmann json now, I've managed to store it and when I do a dump jsonRootNode.dump(), the contents are represented properly.
However I can't find a way to access the contents.
I've tried jsonRootNode["active"], jsonRootNode.get() and using the json::iterator but still can't figure out how to retrieve my contents.
I'm trying to retrieve "active", the array from "list1" and object array from "objList"
The following link explains the ways to access elements in the JSON. In case the link goes out of scope here is the code
#include <json.hpp>
using namespace nlohmann;
int main()
{
// create JSON object
json object =
{
{"the good", "il buono"},
{"the bad", "il cativo"},
{"the ugly", "il brutto"}
};
// output element with key "the ugly"
std::cout << object.at("the ugly") << '\n';
// change element with key "the bad"
object.at("the bad") = "il cattivo";
// output changed array
std::cout << object << '\n';
// try to write at a nonexisting key
try
{
object.at("the fast") = "il rapido";
}
catch (std::out_of_range& e)
{
std::cout << "out of range: " << e.what() << '\n';
}
}
In case anybody else is still looking for the answer.. You can simply access the contents using the same method as for writing to an nlohmann::json object. For example to get values from
json in the question:
{
"active" : false,
"list1" : ["A", "B", "C"],
"objList" : [
{
"key1" : "value1",
"key2" : [ 0, 1 ]
}
]
}
just do:
nlohmann::json jsonData = nlohmann::json::parse(your_json);
std::cout << jsonData["active"] << std::endl; // returns boolean
std::cout << jsonData["list1"] << std::endl; // returns array
If the "objList" was just an object, you can retrieve its values just by:
std::cout << jsonData["objList"]["key1"] << std::endl; // returns string
std::cout << jsonData["objList"]["key2"] << std::endl; // returns array
But since "objList" is a list of key/value pairs, to access its values use:
for(auto &array : jsonData["objList"]) {
std::cout << array["key1"] << std::endl; // returns string
std::cout << array["key2"] << std::endl; // returns array
}
The loop runs only once considering "objList" is array of size 1.
Hope it helps someone
I really like to use this in C++:
for (auto& el : object["list1"].items())
{
std::cout << el.value() << '\n';
}
It will loop over the the array.

Processing array of generic BSON documents with MongoDB C++ driver

I have the following document in my MongoDB test database:
> db.a.find().pretty()
{
"_id" : ObjectId("5113d680732fb764c4464fdf"),
"x" : [
{
"a" : 1,
"b" : 2
},
{
"a" : 3,
"b" : 4
}
]
}
I'm trying to access and process the elements in the "x" array. However, it seems that the Mongo driver is identifying it not as an array of JSON document, but as Date type, as shown in the following code:
auto_ptr<DBClientCursor> cursor = c.query("test.a", BSONObj());
while (cursor->more()) {
BSONObj r = cursor->next();
cout << r.toString() << std::endl;
}
which output is:
{ _id: ObjectId('51138456732fb764c4464fde'), x: new Date(1360233558334) }
I'm trying to follow the documentation in http://api.mongodb.org/cplusplus and http://docs.mongodb.org/ecosystem/drivers/cpp-bson-array-examples/, but it is quite poor. I have found other examples of processing arrays, but always with simple types (e.g. array of integer), but not when the elements in the array are BSON documents themselves.
Do you have some code example of procesing arrays which elements are generic BSON elements, please?
you could use the .Array() method or the getFieldDotted() method: as in the following:
Query query = Query();
auto_ptr<DBClientCursor> cursor = myConn.query("test.a", query);
while( cursor->more() ) {
BSONObj nextObject = cursor->next();
cout << nextObject["x"].Array()[0]["a"] << endl;
cout << nextObject.getFieldDotted("x.0.a") << endl;
}
At the end, it seems that embeddedObject() method was the key:
auto_ptr<DBClientCursor> cursor = c.query("test.a", BSONObj());
while (cursor->more()) {
BSONObj r = cursor->next();
cout << "Processing JSON document: " << r.toString() << std::endl;
std::vector<BSONElement> be = r.getField("x").Array();
for (unsigned int i = 0; i<be.size(); i++) {
cout << "Processing array element: " << be[i].toString() << std::endl;
cout << " of type: " << be[i].type() << std::endl;
BSONObj bo = be[i].embeddedObject();
cout << "Processing a field: " << bo.getField("a").toString() << std::endl;
cout << "Processing b field: " << bo.getField("b").toString() << std::endl;
}
}
I was wrongly retrieving a different ObjectID and a different type (Date instead of array) becuase I was looking to a different collection :$
Sorry for the noise. I hope that the fragment above helps others to figure out how to manipulate arrays using the MongoDB C++ driver.