add data to QJsonArray in a loop with C++ Qt - c++

I'm not sure what the correct way to create nested JSON from a loop is in Qt. Here's an example of what I mean
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QSet>
#include <QString>
#include <QVector>
int main(int argc, char *argv[]) {
QVector<QSet<int>> sets;
QSet<int> testSet;
QJsonDocument doc;
QJsonArray root;
doc.setArray(root);
testSet.insert(0);
testSet.insert(1);
testSet.insert(2);
QSet<int> testSet1;
testSet1.insert(3);
testSet1.insert(4);
sets.push_back(testSet);
sets.push_back(testSet1);
for (auto set : sets) {
QJsonArray setJSON;
for (int i : set) {
setJSON.append(i);
}
root.append(setJSON);
}
qDebug(doc.toJson()); // [ ] -- empty?
}
In the code above, I have a list of sets of integers. I want to represent it as a nested array in JSON. This doesn't work though, because the QJson methods are all pass by reference, meaning that when the scope of the code changes, for instance leaving one of the for loops, the data is destroyed. Is the only way then to do this to create the nested objects on the heap using new? That seems strange to me.

Until now, Qt JSON works only with instance copy, as you see that QJson object is passed as const reference (&) in every set function.
The first QJsonDocument::setArray() take a copy of root instance as its main object. Change of root content after does not affect doc content.
You can verify the root content after the for block :
qDebug() << QJsonDocument( root ).toJson();
The doc variable should be assigned at where you want to take the actual root content:
doc = QJsonDocument (root);
Or:
doc.setArray(root)

Related

Unable to cast afterCreate event to PdxInstance when cache is updated through region->put

I was able to cast the cache listener's afterCreate event value to the PdxInstance data type when the region was updated through Geode's REST API. The code below ran fine without any issues. However when I tried updating the region through the custom defined pdx serialization using the Order.hpp and Order.cpp example code that Geode provided, the dynamic cast in my defined cache listener below returned a value with memory address 0x0 and so when it tries to access the fields in the order object, it's giving me segmentation fault. The objectSize() method also returned 0 bytes which suggests to me that the pdx serialisation was not done correctly.
Would someone be able to advise me what I've done incorrectly below?
#include <iostream>
#include <sstream>
#include <string>
#include <geode/CacheFactory.hpp>
#include <geode/PoolManager.hpp>
#include <geode/RegionFactory.hpp>
#include <geode/RegionShortcut.hpp>
#include <geode/TypeRegistry.hpp>
#include <geode/AttributesMutator.hpp>
#include <geode/CacheListener.hpp>
#include <geode/EntryEvent.hpp>
#include <geode/CacheableString.hpp>
#include <geode/PdxInstance.hpp>
#include "Order.hpp"
using namespace apache::geode::client;
using namespace customserializable;
class MyEventHandler : public CacheListener
{
public:
void afterCreate(const EntryEvent& event) override
{
auto key(dynamic_cast<CacheableString *>(event.getKey().get()));
std::cout << "afterCreate: " << key->value() << std::endl;
auto mypdx = event.getNewValue().get();
mypdx->toString();
mypdx->objectSize();
PdxInstance *pdx = dynamic_cast<PdxInstance *>(mypdx);
// auto pdx(dynamic_cast<PdxInstance *>(mypdx));
int order_id = pdx->getByteField("order_id");
int quantity = pdx->getByteField("quantity");
std::string name = pdx->getStringField("name");
std::cout << "Testing" << std::endl;
}
};
auto MyListener = std::make_shared<MyEventHandler>();
int main(int argc, char** argv) {
auto cache = CacheFactory()
.set("log-level", "none")
.setPdxReadSerialized(true)
.create();
cache.getPoolManager()
.createFactory()
.addLocator("localhost", 10334)
.setSubscriptionEnabled(true)
.create("pool");
auto regionFactory = cache.createRegionFactory(RegionShortcut::CACHING_PROXY);
auto region = regionFactory.setPoolName("pool").create("custom_orders");
cache.getTypeRegistry().registerPdxType(Order::createDeserializable);
region->getAttributesMutator()->setCacheListener(MyListener);
std::cout << "Create orders" << std::endl;
auto order1 = std::make_shared<Order>(1, "product x", 23);
auto order2 = std::make_shared<Order>(2, "product y", 37);
region->registerAllKeys();
region->put("Order19" , order1);
cache.close();
}
To be clear, what we believe you need here is dynamic_cast<Order*>(mypdx);. Let us know how things turn out!
Geode client developer here,
Order derives from PdxSerializable, not PdxInstance. If you look at their inheritance hierarchies, you'll see they're siblings to one another, not one being the descendant of the other. Dynamic casts are, among other things, also a kind of runtime type query, so you're effectively asking if mypdx derives from PdxInstance, and returning a nullptr is the result indicating false, which is correct. Change the type in your cast, and I think you'll have much more satisfying results.
Good luck, and keep asking questions, we do try to pay attention and respond.

C++ string declaration and Qt issue

cpp file:
#include "currency.hpp"
currencyNames[4] = {"gbp","usd","eur","aud"};
QComboBox *box1 = new QComboBox();
int i;
for(i=0; i < 4; i++){
QString *s = QString::string(currencyNames[i]);
box1->addItem(s);
}
hpp file:
#pragma once
#include string
.
.
.
static const int SIZE = 4;
std::string currencyNames[SIZE];
I keep getting a number of errors, I want an array containing the information above then loop through the array adding it to the QComboBox. Have no success. All relevant Qt headers included.
Besides the issues already stated in previous comments QComboBox::addItem method takes a reference to QString not a pointer.
Since you decided to use Qt framework you can embrace its collections which allows better interoperability with various widgets. Hence, your example could be rewritten a bit simplier.
For example:
QStringList currencyNames{"gbp","usd","eur","aud"};
QComboBox *box = new QComboBox();
box->addItems(currencyNames);
Just remember to assign the box to some parent to make it handle the box destruction when appropriate.

Properly storing QFiles in a collection

What is the best way to store multiple QFile in a collection? Would be best to use QList, array, or vectors?
I want to access the element of the collection as a QFile and not QFile*.
I'm trying to read a directory in Qt and store all the files in a collection. Then print all the file names to the screen.
How I'm reading the directory:
QDirIterator it(p_dir, QStringList() << "*.wav", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext())
{
// Put file into collection
}
Currently I'm using QList to store the QFiles:
QList<QFile*> list;
I read that the best way to get file information is to use QFileInfo type. How could I set the QFileInfo file to be the element from my list?
I want to do something like:
QFileInfo info;
QListIterator<QFile*> i(list);
while(i.hasNext())
{
info.setFile(i);
}
But the above gives me:
error: no matching function for call to 'QFileInfo::setFile(QListIterator&)'
fi.setFile(i);
Keep in mind QFile inherits QObject, and as such is not copyable or movable. It is common practice to store pointers to such objects in containers rather than the objects themselves. As for which container to use, it doesn't really matter much, but QVector will be the most efficient.
QListIterator is a Java style iterator. So:
QListIterator::QListIterator(const QList<T> & list)
Constructs an iterator for traversing list. The iterator is set to be
at the front of the list (before the first item).
The iterator doesn't point to anything, if you want to get the actual object, use i.next() to give you a QFile* which you then will have to dereference, since setFile() takes in a QFile &, not a QFile *.
QFile is not copyable. In C++98, there's generally no way of storing it in a container, unless the container supports in-place construction. I don't know offhand of any such C++98 containers, although writing one wouldn't be all that hard.
In C++11, you can use any container that doesn't need to copy elements and supports emplacement, e.g. std::list:
// https://github.com/KubaO/stackoverflown/tree/master/questions/qfile-list-36391586
#include <QtCore>
#include <list>
void populate(std::list<QFile> & files) {
QDir dir(QCoreApplication::applicationDirPath());
for (auto fileName : dir.entryList(QDir::Files)) {
qDebug() << "adding file" << fileName;
files.emplace_back(fileName);
}
}
void iterate(std::list<QFile> & files) {
for (auto & file : files)
if (file.open(QIODevice::ReadOnly)) {
qDebug() << "successfully opened" << file.fileName();
}
}
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
std::list<QFile> files;
populate(files);
iterate(files);
}
On my system, when run from Qt Creator, the output is:
adding file "main.o"
adding file "Makefile"
adding file "qfile-list-36391586"
successfully opened "main.o"
successfully opened "Makefile"
successfully opened "qfile-list-36391586"
Since QFile is a proper class implementing RAII of system resources, the files will get automatically closed by files::~list, before main returns.
I'm currently manipulating QFileInfo based on certain criterian/logics..but if I just filter all the complicated logic, it would be something like
QDir testDir("/home/mypath/to/dir");
QFileInfoList files = testDir.entryInfoList(); // #1. here you may use filters as I'm filtering based on a specific file extension
QFileInfoList myfiles;
for(int index = 0; index < files.size(); ++index)
{
const QFileInfo& info = files.at(index);
qDebug() << info.absoluteFilePath() << endl;
// #2. I've some sort of filters I used in #1 above that I compares here by utilizing info here, something like
//if (info.baseName().contains("anyPattern");
myfiles.push_back(info);
}
// Now you can view whatever you stored
for (int vw_idx = 0; vw_idx < myfiles.size(); ++vw_idx)
{
const QFileInfo& info = files.at(index);
qDebug() << info.absoluteFilePath() << endl;
}

How to properly add strings to QListWidgets?

In the following code the method exec(const char *cmd) runs a bash script and returns the output as a vector of strings. The intent is to create a QListWidgetItem out of each of those strings and add them to a QListWidget on a little GUI that I have created, but the QListWidgetItems are not created successfully. I always seem to be required to use a const char* to create either a QString or QListWidgetItem, it will not allow me to create one using a string variable.
You can see what I am going for in the line: QString nextLine = txtVcr.back();
There is an exception thrown here, it wants QString set to a const char*, for example QString nextLine = "Hello, World!";
How do I go about getting the strings from my vector and creating QListWidgetItems out of them to add to my QListWidget?
In C# everything was rather direct in that I could add strings or whatever else to any container/widget. Is there an intermediate step that I am overlooking with these "QWidgets"? Perhaps I should be casting to "Q" types?
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
vector<string> exec(const char *cmd);
vector<string> txtVcr = exec("/home/rhurac/getServices.sh");
while (!txtVcr.empty())
{
QString nextLine = txtVcr.back();
ui->uxListWidget->addItem(new QListWidgetItem(nextLine, ui->uxListWidget));
txtVcr.pop_back();
}
}
To get a QStringList from a std::vector<std::string>, you'll need to use QString::fromStdString on all the elements. For example:
#include <QStringList>
#include <algorithm>
#include <string>
#include <vector>
QStringList convert(const std::vector<std::string>& v)
{
using std::begin;
using std::end;
QStringList l;
l.reserve(v.size());
std::transform(begin(v), end(v), std::back_inserter(l),
&QString::fromStdString);
return l;
}
Then you can populate a QStringListModel feeding a QListView, as suggested in other answer.
Simply don't use QListWidgets and other QxyzWidget classes. They are depricated, and left in Qt for compatibility with old code (Qt3 basically).
Use QListView and QStringListModel for your use-case. E.g.
QListView *lv = new QListView();
QStringListModel m;
QStringList data = QStringList()<<"AAA"<<"BBB"<<"CCC";
m.setStringList(data);
lv->setModel(&m);
lv->show();
P.S.: Sorry, it doesn't answer your question directly. But unless you have to support legacy code, don't touch QListWidgets!

Qt QList does not append local Objects

I have a understanding-problem why following code does not store any QString-Objects into my QList
QMap<QString, QList<QString> >map;
map = QMap<QString, QList<QString> >();
map.insert("eins", QList<QString>());
QList<QString> listInMap = map["eins"];
listInMap.append("test1");
listInMap.append("test2");
QList<QString> copyOfListInMap = map.value("eins");
qDebug() << copyOfListInMap.count();
Output: 0
The reason is simple: copy on write, aka. implicit sharing
QList<QString> listInMap = map["eins"];
At this point, you have not yet got a hard copy, only a "reference". That is not fair in the standard C++ sense, but imagine it as a "shallow copy". However, when you start appending in here, the list will copied and the original will left empty. It is because QList is implemented the way that is CoW (copy-on-write).
listInMap.append("test1");
listInMap.append("test2");
On a side note, you may wish to take a look at QStringList. While it inherits QList, it also has some additional convenience methods.
Now, you may be asking: How am I supposed to fill my map in, then?.
Multi map
I would personally suggest to use QMultiMap or at least QMap with insertMulti.
main.cpp
#include <QMap>
#include <QDebug>
#include <QString>
int main()
{
QMultiMap<QString, QString> map;
map.insert("eins", "test1");
map.insert("eins", "test2");
qDebug() << map.count();
return 0;
}
main.pro
TEMPLATE = app
TARGET = main
QT = core
SOURCES += main.cpp
Build and Run
qmake && make && ./main
Output
2
Single map
If you stick to the current approach, I would suggest to either append the value into a new QStringList with which you will overwrite the value, or hold a reference to the exact same list.
Having said that, in your case, it looks a bit overkill to even consider an external storage for insertion. You ought to do that right away in my humble opinion.
main.cpp
#include <QMap>
#include <QDebug>
#include <QString>
int main()
{
QMap<QString, QStringList> map;
map.insert("eins", QStringList{"test1", "test2"});
qDebug() << map.value("eins").count();
return 0;
}
main.pro
TEMPLATE = app
TARGET = main
QT = core
CONFIG += c++11
SOURCES += main.cpp
Build and Run
qmake && make && ./main
Output
2
According to the documentation your sample should be changed as follow:
QMap<QString, QList<QString> >map;
QList<QString> & listInMap = map["eins"]; // use reference here
listInMap.append("test1");
listInMap.append("test2");
QList<QString> copyOfListInMap = map.value("eins");
qDebug() << copyOfListInMap.count();