I am trying to read JSON objects using the QScriptValue class in Qt and I've noticed that when iterating over an array I always get an extra element at the end.
Let's say I have a string named value like this:
QString value = "{\"identifier\":\"testID\",
\"params\":[{\"field\":\"filed1:\",
\"datatype\":\"integer\",\"fieldend\":\" \"},{\"field\":\"field2:
\",\"datatype\":\"integer\",\"fieldend\":\" \"}]}";
My code for iteration looks like this:
QScriptValue sc;
QScriptEngine engine;
sc = engine.evaluate("("+value+")");
if(sc.isValid())
{
if(sc.property("params").isArray())
{
QScriptValueIterator it(sc.property("params"));
while(it.hasNext())
{
it.next();
qDebug()<< "field:" << it.value().property("field").toString();
qDebug()<< "datatype:" << it.value().property("datatype").toString();
qDebug()<< "fieldend:" << it.value().property("fieldend").toString();
qDebug()<< "--------------";
}
}
}
The output results with an extra element that has empty values:
field: "field1:"
datatype: "integer"
fieldend: " "
--------------
field: "field2: "
datatype: "integer"
fieldend: " "
--------------
field: ""
datatype: ""
fieldend: ""
--------------
I read the documentation of QScriptValueIterator and it says:
The next() advances the iterator. The name(), value() and flags() functions return the name, value and flags of the last item that was
jumped over
So I changed my iteration accordingly:
while(it.hasNext())
{
it.next();
qDebug() << it.name() << " : " << it.value().toString();
qDebug()<< "--------------";
}
But I get something that I did not expect:
"0" : "[object Object]"
--------------
"1" : "[object Object]"
--------------
"length" : "2"
--------------
Can anyone point out what am I doing wrong here?
Thanks.
I faced the same issue and I added this line after the it.next(); line:
if (it.flags() & QScriptValue::SkipInEnumeration) continue;
You can find more info about it here:
http://developer.qt.nokia.com/doc/qt-4.8/qscriptvalueiterator.html#details
Related
I have a weird behavior when using qDeleteAll on a QMap<QString, Record*>.
First I delete my values in the QMap with qDeleteAll and then I use m_data.clear()
to empty the QMap.
void ClassName::function1(QMap<QString, QString> filter)
{
qDebug() << "function1 called";
QString query = "postgrestAdress?";
if (filter.size()){
for (QMap<QString, QString>::iterator i = filter.begin(); i != filter.end(); i++ ) {
query.append("&").append(i.key()).append("=").append(i.value());
}
}
postgrestLambdaFucntion(query,[this, query](QList<QMap<QString,QString>> result)
{
qDeleteAll(m_data);
m_data.clear();
m_users.clear();
for(auto &line : result)
{
Record *record = Mapper::mapQMapToRecord(line);
qDebug() << "created Record:" << record << " with id:" << record->id;
m_data.insert(line.value("material_number")+ "_" +QString::number(record->id), record);
if (!m_users.contains(line.value("user"))){
m_users.append(line.value("user"));
}
}
nextFunction(0);
});
}
Consider this as a record:
class Record : public QObject
{
Q_OBJECT
public:
explicit Record(QObject *parent = nullptr) : QObject(parent){};
~Record() {
qDebug() << "record " << this << "with id" << this->id << " is destroyed";
};
int id = 0;
QString material_number = "";
QString user = "";
}
When I call function1 with an already populated m_data list I get this debug information:
>Returing table from postgrest...
>record Record(0x564813686040) with id 1 is destroyed
>record Record(0x5648136f74b0) with id 33 is destroyed
>record Record(0x56481376a6b0) with id 15 is destroyed
>record Record(0x564813708c00) with id 43 is destroyed
>record Record(0x564813c28090) with id 44 is destroyed
>record Record(0x564813768eb0) with id 2 is destroyed
>record Record(0x5648136eb7a0) with id 14 is destroyed
>created Record: Record(0x564813768eb0) with id: 15
>created Record: Record(0x564813c28090) with id: 2
>created Record: Record(0x564813708c00) with id: 43
>created Record: Record(0x56481376a6b0) with id: 33
>created Record: Record(0x5648136f74b0) with id: 14
>created Record: Record(0x564813686040) with id: 1
>created Record: Record(0x564813de45b0) with id: 44
Inside the Mapper-Function I create new Record-Classes
Record* Mapper::mapQMapToRecord(QMap<QString, QString> line)
{
Record* record = new Record(nullptr);
record->id = line.value("id").toInt();
record->user = line.value("user");
return record;
}
Note that the Memory-Address of those new created objects are the same
as the ones qDeleteAll removed.
In the next function I make more requests and add data to the records,
which are now "not available" and I get nullptr exceptions.
If I don't use "qDeleteAll" it works.
I can also use a for-loop and use "deleteLater" from QObject
without errors.
I just want to know this behaviour better or why it happens.
(it happens in debug and release builds)
Anybody has insights for me?
Best Regards
exa.byte
I have a json like this:
{
"name1": {
"path": "D:/myfolder"
},
"name2": {
"path": "D:/anotherFolder"
},
"name3": {
"path": "D:/myfolder"
}
}
And I want to get: are the names and the folder names.
like :
name1, D:/myfolder
name2, D:/anotherFolder
name3, D:/myfolder
It is not important if i get them independently or in an map, because i want to save them
to new variables into another struct.
what I have so far is:
QString jsonFile = "myjson.json";
QFile loadFile(jsonFile);
if(!loadFile.open(QIODevice::ReadOnly)){
qDebug() << "Failed to open " << loadFile; }
QByteArray data = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(data));
QJsonObject json = loadDoc.object();
QStringList keys = json.keys(); // <------- that give me the names. check.
But i didnt get it: how to get the second element?!?
the nearest approche so far was to get the "2nd dimension" with foreach:
foreach(const QJsonValue path, json ){
qInfo() << path;
}
this gave me as output:
QJsonValue(object, QJsonObject({"path": "D:/myfolder"}))
QJsonValue(object, QJsonObject({"path": "D:/anotherFolder"}))
QJsonValue(object, QJsonObject({"path": "D:/myfolder"}))
Most optimal way to do it just using single for iteration
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject json = doc.object();
for (auto it = json.constBegin(); it != json.constEnd(); ++it)
{
qInfo() << it.key() << it.value().toObject().value("path").toString();
}
You have to iterate through the keys obtaining the value, and then access the last one using the key "path":
QJsonDocument loadDoc = QJsonDocument::fromJson(data);
QJsonObject json = loadDoc.object();
for(const QString & key : json.keys()){
qInfo() << key << json[key].toObject()["path"].toString();
}
I have a program that modifies a JSON document if necessary. The program has to add a child to another value whether or not it's an already an object. The program should behave like so:
If the object with key "x" does not exist, create object with key "x" and add value y as a child.
If the object with key "x" DOES exist, set value y as a child.
If the key "x" exists and is ANY OTHER type, delete it, create an object with the key "x" and then add value y as a child.
I see ways to test if property tree values exist or whether they are specified types, but none to test if it's an object or not an object.
Here's a simple program I made illustrating what I mean:
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <sstream>
#include <iostream>
const char *json = "{"
"\"object\" : { \"mighty\" : \"wind\" },"
"\"boolean\" : true"
"}";
void printTree( std::string name, boost::property_tree::ptree tree )
{
std::cout << "Pass '" << name << "'" << std::endl;
try
{
std::stringstream ss;
boost::property_tree::write_json( ss, tree );
std::cout << ss.str() << std::endl;
}
catch( std::exception &e )
{
std::cout << "Could not make create json: " << e.what() << std::endl;
}
}
int main( int argc, char *argv[] )
{
boost::property_tree::ptree tree;
// Load it
std::istringstream ss_json( json );
boost::property_tree::read_json( ss_json, tree );
// Add a value to an object that doesn't exist
tree.put( "none.value", "hello!" );
// Print to see
printTree( "Nonexistent value test", tree );
// Add a value to the object
tree.put( "object.value", "bello!" );
// Print this one
printTree( "Adding value test", tree );
// Convert boolean to an object and add a value
tree.put( "boolean.value", "mello!" );
// Print it
printTree( "Converting value test", tree );
}
The output will be:
Pass 'Nonexistent value test'
{
"object": {
"mighty": "wind"
},
"boolean": "true",
"none": {
"value": "hello!"
}
}
Pass 'Adding value test'
{
"object": {
"mighty": "wind",
"value": "bello!"
},
"boolean": "true",
"none": {
"value": "hello!"
}
}
Pass 'Converting value test'
Could not make create json: <unspecified file>: ptree contains data that cannot be represented in JSON format
You can see in the output, the last step fails to convert to JSON (doesn't throw when I try to set it).
How can I achieve scenario 3 in my list above?
If the key "x" exists and is ANY OTHER type, delete it, create an object with the key "x" and then add value y as a child. Also, they don't observe any of the JSON data types.
Your plan is pretty doomed. Property Tree is not a JSON library. Property Trees can have data and child nodes at the same node. E.g.
ptree p;
auto& x = p.put_child("x", {});
x.put_value("hello");
write_json(std::cout, p);
Prints
{
"x": "hello"
}
But adding
/*auto& a = */ p.put_child("x.a", {});
write_json(std::cout, p);
Fails with Live On Coliru
terminate called after throwing an instance of 'boost::wrapexcept<boost::property_tree::json_parser::json_parser_error>'
what(): <unspecified file>: ptree contains data that cannot be represented in JSON format
A workaround would be to remove any value prior to or when adding properties:
x.put_value("");
auto& a = p.put_child("x.a", {});
a.add("prop1", 123);
a.add("prop2", "one two three");
a.add("b.prop1", "nesting");
write_json(std::cout, p);
Would print Live On Coliru
Finer notes
It might seem more efficient to check the presence of a value before clearing it:
if (x.get_value_optional<std::string>()) {
x.put_value("");
}
But due the the stringly typed nature of Property Tree storage there's no difference as the condition will just always be true for std::string. (Similarly there's no way to retrieve a value by reference.)
Note ALSO that when setting the n.prop1 nested property, you MAY have to also check that b has no value if you don't control the source data, because otherwise it would fail again.
Assuming that your object graph structure is reasonably predictable (or even static), I'd suggest getting it over with ahead of time:
for (auto key : { "x", "x.a", "x.a.b" }) {
if (auto child = p.get_child_optional(key)) {
std::cout << "clearing " << key << std::endl;
child->put_value("");
}
}
Which can be generalized with a helper:
clear_values("x.a.b", p);
Which could be implemented as
void clear_values(ptree::path_type path, ptree& p) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (child) {
child->put_value("");
clear_values(path, *child);
}
}
Bonus
In fact with such a helper it might become opportune to also create the expected hierarchy on the fly:
void clear_values(ptree::path_type path, ptree& p, bool create = false) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (!child && create) {
child = p.put_child(head, {});
}
if (child) {
child->put_value("");
clear_values(path, *child, create);
}
}
Now it would even work well without any pre-existing data:
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
void clear_values(ptree::path_type path, ptree& p, bool create = false) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (!child && create) {
child = p.put_child(head, {});
}
if (child) {
child->put_value("");
clear_values(path, *child, create);
}
}
int main() {
ptree p;
clear_values("x.a.b", p, true);
auto& a = p.get_child("x.a");
a.add("prop1", 123);
a.add("prop2", "one two three");
a.add("b.prop1", "nesting");
write_json(std::cout, p);
}
Prints
{
"x": {
"a": {
"b": {
"prop1": "nesting"
},
"prop1": "123",
"prop2": "one two three"
}
}
}
I am trying to parse json with Qt but I have no success. This is the ouput that I get from the server:
[{"anni":2019},{"anni":2018},{"anni":2017}]
Where that's generated from this simple php:
header('Content-Type: application/json');
echo json_encode($data);
The $data is an array containing the values you see above. I am using this piece of code in Qt 5.11.2:
void MainWindow::showYears() {
//reply is a QNetworkReply* reply;
if (reply->error() != QNetworkReply::NoError) {
//some error managment
} else {
auto responsedata = reply->readAll();
QJsonArray years = QJsonDocument::fromJson(responsedata).array();
qDebug() << QString{responsedata};
for(const QJsonValue& y : years) {
QJsonObject obj = y.toObject();
//doing "qDebug() << r" shows that r is "" (empty!)
auto r = obj["anni"].toString();
ui->comboBoxP->addItem(r);
}
}
}
What's wrong here?
Please note that qDebug() << QString{responsedata}; prints "[{\"anni\":2019},{\"anni\":2018},{\"anni\":2017}]"
The value for your field anni is an integer. Using the member function toString will not convert it to a string representation. it will return NULL. http://doc.qt.io/qt-5/qjsonvalue.html#toString
Try with: auto r = QString::number(obj["anni"].toInt());
Im trying to save my waypoints into .json file, but it is always saving the wrong string.. which I dont know where it is coming.
This is my code:
for (int i = 1; i < path->GetPointCount(); i++) {
Vector lonlatalt;
path->GetPointLocation(i).GetGeodetic(&lonlatalt);
sstr << "waypoint";
sstr << i;
sstr << " ";
sstr << lonlatalt.x;
sstr << " ";
sstr << lonlatalt.y;
sstr << " 0.0";
sstr << std::endl;
std::string alt = " 0.0";
QJsonObject json;
json["waypoint"+i] = lonlatalt.x, lonlatalt.y, alt;
QJsonDocument json_doc(json);
QString json_string = json_doc.toJson();
QString filename = "./tempwpf";
QFile save_file(filename);
if(!save_file.open(QIODevice::WriteOnly)){
qDebug() << "failed to open save file";
}
save_file.write(json_string.toLocal8Bit());
save_file.close();
}
The output of json file should be:
{
"waypoint0": "10.05456, 51.02453, 0.0",
"waypoint1": "10.05456, 51.02453, 0.0",
"waypoint2": "10.05456, 51.02453, 0.0",
"waypoint3": "10.05456, 51.02453, 0.0",
"waypoint4": "10.05456, 51.02453, 0.0",
"waypoint5": "10.05456, 51.02453, 0.0",
"waypoint6": "10.05456, 51.02453, 0.0"
}
json["waypoint"+i] = lonlatalt.x, lonlatalt.y, alt;
There's not a single right thing in this line.
"waypoint"+i
"waypoint" is a string literal, i.e. an array of char, which decays to a pointer to its first element; now, if you sum an integer to a pointer, you get an incremented pointer, i.e. a pointer that points i elements after the first.
Hence, here you are effectively taking a substring of "waypoint" starting at the i-th character. "waypoint"+1 => "aypoint"; "waypoint"+2 => "ypoint" and so on.
Given that you are using Qt and QString anyhow to put this into the JSON document, you can simply use the Qt string formatting methods; so, on the left hand side you can do:
json[QString("waypoint%1").arg(i)]
On the right hand side, it's all wrong again; here you are mistakingly using the comma operator, which evaluates the expressions at both its sides and discards the left one; so when you write
lonlatalt.x, lonlatalt.y, alt
you are actually writing
(lonlatalt.x, lonlatalt.y), alt
which boils down to plain alt.
Now, if you want to format this as a comma-separated string, you can again use the QString methods:
json[QString("waypoint%1").arg(i)] = QString("%1, %2, 0.0").arg(lonlatalt.x, 0, 'f', 5).arg(lonlatalt.y, 0, 'f', 5)
Finally, you are creating/saving a new QJsonDocument at each iteration, so every time you create a file with just one waypoint and overwrite the previous one. You should move the creation/save outside of the loop:
QJsonObject json;
goes before the for
json["waypoint"+i] = lonlatalt.x, lonlatalt.y, alt;
remains inside, and all the rest outside. Notice that even the code to save is suboptimal: json_doc.toJson() returns a QByteArray already in UTF-8 (which is generally used as the "canonical" encoding for JSON); you are reconverting it to a QString and then converting it back to an 8 bit encoding (which is lossy and most probably unintended).
Also, you check if the file was opened successfully, and write on it even in case of error - that's definitely a bad idea.
Finally, close() is not needed - it's performed automatically when the QFile goes out of scope.
To sum it up:
QFile save_file("./tempwpf");
if(save_file.open(QIODevice::WriteOnly)){
QJsonDocument json_doc(json);
save_file.write(json_string.toJson());
} else {
qDebug() << "failed to open save file";
}