How to get QJsonObject values in order? - c++

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?

Related

How to extract value from json array with QJsonDocument format

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();

instead of so many if statements I want a logic where I can do it with one statement or optimized way

I have a vector of structures vector<S> sData which my structure contains 5 elements (name, age, techs, projects, language)
The user can input name and it should output the structures that equal to that name and for the other elements too.
My problem is that the user can input two of them only and it should only check for those like user can input age and language it should output the list of people who have the same age and language. If you imagine you can write so many if statements so it will give a right output because you have 5 elements,
and the user can choose 5 of them 4 and randomly he can input 3 or 4 of them and my program should understand that logic.
In my code I'm writing with Qt widget application. When the user didn't enter any of the strings(name, techs, projects, rate), the default value is NULL and the age is -1.
struct S
{
QString name;
int age;
QString techs;
QString projects;
QString rate;
};
QVector<S> sData;
QVector<int> indexData;
//this is how i did for the name
indexData.clear();
if(searchName!=NULL)//i don't want to write this if for name and age ||
// name and tech ... then age and tech etc.
{
for(int i=0;i<sData.count();++i)
{
if(searchName==sData[i].name)
{
indexData.push_back(i);
}
}
}
I'd suggest a little code refactor. Let's consider adding a helper structure SearchS which holds all params you are looking for (and comparison operator):
struct S {
QString name;
int age = -1;
QString techs;
QString projects;
QString rate;
};
struct SearchS {
std::optional<QString> name;
std::optional<int> age;
std::optional<QString> techs;
std::optional<QString> projects;
std::optional<QString> rate;
static bool string_match(const QString& s, const std::optional<QString>& search_s) {
return !search_s || *search_s == s;
}
bool operator==(const S& s) const noexcept {
return string_match(s.name, name) &&
string_match(s.techs, techs) &&
string_match(s.projects, projects) &&
string_match(s.rate, rate) &&
(!age || *age == s.age);
}
};
This allows you to iterate over data vector and filter results.
The cleanest way to do this I think is using ranges-v3:
std::vector<S> sData;
SearchS target;
target.name = "John";
target.age = 28;
// ... other params if needed ...
auto filtered_items = data | ranges::views::filter([&target](const auto& item) {
return target == item;
});
Here you can apply other filters... Or iterate over the filtered results:
for(auto&& item : filtered_items) {
// ... add your code here ...
}
Does this help?
struct S
{
QString name;
int age;
QString techs;
QString projects;
QString rate;
};
QVector<S> sData;
QVector<int> indexData;
int main() {
searchName = ...;
searchAge = ...;
searchTechs = ...;
...
auto it = std::find_if(std::begin(sData), std::end(sData),
[&searchName, &searchAge, &searchTecs, ...](const S& s)
{
bool eq = true;
if (searchName)
eq &= (searchName == s.name);
if (searchAge)
eq &= (searchAge == s.age);
if (searchTechs)
eq &= (searchTechs == s.techs);
....
return eq;
}
if (it == std::end(sData))
std::terminate(); //element not found
// *it is the desired element
}
or if you want to find every element matching
int i = 0;
for (const S& s : sData) {
bool eq = true;
if (searchName)
eq &= (searchName == s.name);
if (searchAge)
eq &= (searchAge == s.age);
if (searchTechs)
eq &= (searchTechs == s.techs);
....
if (eq)
indexData.push_back(i);
++i;
}

Cannot dereference end list iterator

I'm currently working on a project about browser histories. I'm using the STL's list implementation to keep track of the different websites. I have most of it figured out but I can't seem to resolve the error:
Screenshot; cannot dereference end list iterator.
The website data is contained within my Site objects.
class BrowserHistory {
private:
list<Site> history;
list<Site>::iterator current = history.begin();
public:
void visitSite(string, size_t)
void backButton();
void forwardButton();
void readFile(string);
};
void BrowserHistory::visitSite(string x, size_t y)
{
while (current != history.end()) {
history.pop_back();
}
history.push_back({ x, y });
current++;
}
void BrowserHistory::backButton()
{
if (current != history.begin())
current--;
}
void BrowserHistory::forwardButton()
{
if (current != history.end())
current++;
}
void BrowserHistory::readFile(string filename)
{
string action, url;
size_t pageSize;
ifstream dataIn;
dataIn.open(filename);
while (!dataIn.eof()) {
dataIn >> action;
if (action == "visit") {
dataIn >> url >> pageSize;
history.push_back({ url, pageSize });
current++;
}
if (action == "back") {
current--;
}
if (action == "forward") {
current++;
}
}
dataIn.close();
}
Can anyone explain to me what's wrong? Thanks in advance for any help.
When you initialise current, the list is empty.
Adding elements to the list does not suddenly make it a valid iterator, even though it will be different from end.
It looks like you're thinking of iterators as very much like pointers, but they're not.
Iterators are for iterating and should be considered transient, and not stored for later use.
Use a vector, and use an index for current instead of an iterator.
Further, I would rename the "button" functions (what does the history care about buttons?) to "goBack" and "goForward" and use your actual interface when reading:
void BrowserHistory::readFile(string filename)
{
ifstream dataIn(filename);
string action;
while (dataIn >> action) {
if (action == "visit") {
string url;
size_t pageSize;
if (dataIn >> url >> pageSize) {
visitSite(url, pageSize);
}
else {
// Handle error
}
}
else if (action == "back") {
goBack();
}
else if (action == "forward") {
goForward();
}
else {
// Handle error
}
}
}

How do I return a Null Pointer in a function C++

I am currently working on a bit of code that will search within a vector of type Person (which I have defined in the code and will show if needed). If it finds the person, it returns their name. This is currently working, but if it does not find the person, it is supposed to return a Null pointer. The problem is, I cannot figure out how to make it return a Null pointer! It just keeps either crashing the program every time.
Code:
Person* lookForName(vector<Person*> names, string input)
{
string searchName = input;
string foundName;
for (int i = 0; i < names.size(); i++) {
Person* p = names[i];
if (p->getName() == input) {
p->getName();
return p; //This works fine. No problems here
break;
} else {
//Not working Person* p = NULL; <---Here is where the error is happening
return p;
}
}
}
You could use std::find_if algorithm:
Person * lookForName(vector<Person*> &names, const std::string& input)
{
auto it = std::find_if(names.begin(), names.end(),
[&input](Person* p){ return p->getName() == input; });
return it != names.end() ? *it : nullptr; // if iterator reaches names.end(), it's not found
}
For C++03 version:
struct isSameName
{
explicit isSameName(const std::string& name)
: name_(name)
{
}
bool operator()(Person* p)
{
return p->getName() == name_;
}
std::string name_;
};
Person * lookForName(vector<Person*> &names, const std::string& input)
{
vector<Person*>::iterator it = std::find_if(names.begin(), names.end(),
isSameName(input));
return it != names.end() ? *it : NULL;
}
If the name you are searching for is not at the first element, then you are not searching in the rest of the elements.
You need to do something like -
for (int i = 0; i<names.size(); i++){
Person* p = names[i];
if (p->getName() == input) {
return p;
// Placing break statement here has no meaning as it won't be executed.
}
}
// Flow reaches here if the name is not found in the vector. So, just return NULL
return NULL;
As Chris suggested, try using std::find_if algorithm.
Looks like you just have to return Null, nullptr, or 0.
codeproject
Just use following code:
return NULL;

Searching a tree and outputting the path to that node in C++

Say we have the following structure:
struct ATree {
string id;
int numof_children;
ATree *children[5];
};
How would I be able to search for an id and output the path to that id? I have a way of finding if the id is in the tree, but outputting the proper path is another story. I have tried using a string stream, but it doesn't seem to work properly (i get paths that include ids not leading to the id I want). NOTE: assume ids may only appear once in the tree
Should this be done using recursion? Or can it be done using loops?
I Belive that the following code give you a notion of what you should do (recursion):
bool find(const string& i_id, const ATree* i_tree, string& o_path) {
if(!i_tree) return false;
if (i_id == i_tree->id) {
o_path = i_tree->id;
return true;
}
string path;
for (size_t i = 0; i < i_tree->numof_children; ++i) {
if (find(id, i_tree->children[i], path)) {
o_path = i_tree->id + '/' + path;
return true;
}
}
return false;
}
You should basically keep the node from which you arrived to the current, for each node you're going through. Then just pop them out and print them when you found the correct path.
If you keep them in a std::stack structure it will be easy for you to just pop them when you're going back after reaching leaves and not finding the needed id.
If you do it recursively then you just have the stack of your calls and it should be enough, if you convert them to loops (iteratively), then you need the std::stack to remember the states, but it's fairly simple, really.
Rough outline of algorithm:
std::vector< const ATree* >
getPath(const ATree* tree, const std::string& id)
{
std::vector< const ATree* > path;
if (tree->id == id) {
path.push_back(tree);
} else {
for (int i=0; i < tree->numof_children; i++) {
std::vector< const ATree* > tmp=
getPath(tree->children[i], id);
if (tmp.size() > 0) {
path.push_back(tree);
path.insert(path.end(), tmp.begin(), tmp.end());
break;
}
}
}
return path;
}