Use Niels Lohmann's JSON library everywhere

We started to use Niels Lohmann's JSON library instead of the one
included in Qt for the Export JSON dialog a while ago because Qt's
implementation showed some problems. To avoid any similar issues and
because Niels' library has a much nicer API, this commit migrates all
our code to his library.
This commit is contained in:
Martin Kleusberg
2019-05-02 19:57:28 +02:00
parent b803e3c49f
commit d344f396b2
7 changed files with 100 additions and 100 deletions

View File

@@ -13,7 +13,6 @@
#include <QImageReader>
#include <QBuffer>
#include <QModelIndex>
#include <QJsonDocument>
#include <QtXml/QDomDocument>
#include <QMessageBox>
#include <QPrinter>
@@ -23,6 +22,9 @@
#include <QTextDocument>
#include <Qsci/qsciscintilla.h>
#include <json.hpp>
using json = nlohmann::json;
EditDialog::EditDialog(QWidget* parent)
: QDialog(parent),
@@ -479,29 +481,33 @@ void EditDialog::accept()
}
case DockTextEdit::JSON:
{
sciEdit->clearErrorIndicators();
QString oldData = currentIndex.data(Qt::EditRole).toString();
QString newData;
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(sciEdit->text().toUtf8(), &parseError);
bool proceed;
bool proceed = true;
json jsonDoc;
sciEdit->clearErrorIndicators();
if (parseError.error != QJsonParseError::NoError)
sciEdit->setErrorIndicator(parseError.offset-1);
try {
jsonDoc = json::parse(sciEdit->text().toStdString());
} catch(json::parse_error& parseError) {
sciEdit->setErrorIndicator(static_cast<int>(parseError.byte - 1));
if (!jsonDoc.isNull()) {
proceed = promptInvalidData("JSON", parseError.what());
}
if (!jsonDoc.is_null()) {
if (mustIndentAndCompact)
// Compact the JSON data before storing
newData = QString(jsonDoc.toJson(QJsonDocument::Compact));
newData = QString::fromStdString(jsonDoc.dump());
else
newData = sciEdit->text();
proceed = (oldData != newData);
} else {
newData = sciEdit->text();
proceed = (oldData != newData && promptInvalidData("JSON", parseError.errorString()));
}
proceed = proceed && (oldData != newData);
if (proceed)
// The data is different, so commit it back to the database
emit recordTextUpdated(currentIndex, newData.toUtf8(), false);
@@ -577,23 +583,26 @@ void EditDialog::setDataInBuffer(const QByteArray& data, DataSources source)
}
case DockTextEdit::JSON:
{
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(QByteArray(data.constData(), data.size()), &parseError);
sciEdit->clearErrorIndicators();
if (mustIndentAndCompact && !jsonDoc.isNull()) {
json jsonDoc;
try {
jsonDoc = json::parse(std::string(data.constData(), static_cast<size_t>(data.size())));
} catch(json::parse_error& parseError) {
sciEdit->setErrorIndicator(static_cast<int>(parseError.byte - 1));
}
if (mustIndentAndCompact && !jsonDoc.is_null() && !jsonDoc.is_discarded()) {
// Load indented JSON into the JSON editor
textData = QString(jsonDoc.toJson(QJsonDocument::Indented));
textData = QString::fromStdString(jsonDoc.dump(4));
} else {
// Fallback case. The data is not yet valid JSON or no auto-formatting applied.
textData = QString::fromUtf8(data.constData(), data.size());
}
sciEdit->setText(textData);
sciEdit->clearErrorIndicators();
if (parseError.error != QJsonParseError::NoError)
sciEdit->setErrorIndicator(parseError.offset-1);
sciEdit->setEnabled(true);
}
break;
@@ -742,8 +751,8 @@ int EditDialog::checkDataType(const QByteArray& data)
{
if (cellData.startsWith("<?xml"))
return XML;
QJsonDocument jsonDoc = QJsonDocument::fromJson(cellData);
if (!jsonDoc.isNull())
if(!json::parse(cellData, nullptr, false).is_discarded())
return JSON;
else
return Text;

View File

@@ -10,16 +10,17 @@
#include <QDir>
#include <QStandardPaths>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>
#include <QtNetwork/QHttpMultiPart>
#include <QTimeZone>
#include <json.hpp>
#include "RemoteDatabase.h"
#include "version.h"
#include "Settings.h"
#include "sqlite.h"
using json = nlohmann::json;
RemoteDatabase::RemoteDatabase() :
m_manager(new QNetworkAccessManager),
m_configurationManager(new QNetworkConfigurationManager),
@@ -149,7 +150,7 @@ void RemoteDatabase::gotReply(QNetworkReply* reply)
// Add cloned database to list of local databases
QString saveFileAs = localAdd(reply->url().fileName(), reply->property("certfile").toString(),
reply->url(), QUrlQuery(reply->url()).queryItemValue("commit"));
reply->url(), QUrlQuery(reply->url()).queryItemValue("commit").toStdString());
// Save the downloaded data under the generated file name
QFile file(saveFileAs);
@@ -185,15 +186,14 @@ void RemoteDatabase::gotReply(QNetworkReply* reply)
case RequestTypeLicenceList:
{
// Read and check results
QJsonDocument json = QJsonDocument::fromJson(reply->readAll());
if(json.isNull() || !json.isObject())
json obj = json::parse(reply->readAll(), nullptr, false);
if(obj.is_discarded() || !obj.is_object())
break;
QJsonObject obj = json.object();
// Parse data and build licence map (short name -> long name)
QMap<QString, QString> licences;
for(auto it=obj.constBegin();it!=obj.constEnd();++it)
licences.insert(it.key(), it.value().toObject().value("full_name").toString());
QMap<std::string, std::string> licences;
for(auto it=obj.cbegin();it!=obj.cend();++it)
licences.insert(it.key(), it.value()["full_name"]);
// Send licence map to anyone who's interested
emit gotLicenceList(licences);
@@ -202,27 +202,18 @@ void RemoteDatabase::gotReply(QNetworkReply* reply)
case RequestTypeBranchList:
{
// Read and check results
QJsonDocument json = QJsonDocument::fromJson(reply->readAll());
if(json.isNull() || !json.isObject())
json obj = json::parse(reply->readAll(), nullptr, false);
if(obj.is_discarded() || !obj.is_object())
break;
QJsonObject obj = json.object();
QJsonObject obj_branches = obj["branches"].toObject();
json obj_branches = obj["branches"];
// Parse data and assemble branch list
QStringList branches;
for(auto it=obj_branches.constBegin();it!=obj_branches.constEnd();++it)
branches.append(it.key());
std::vector<std::string> branches;
for(auto it=obj_branches.cbegin();it!=obj_branches.cend();++it)
branches.push_back(it.key());
// Get default branch
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
QString default_branch = obj["default_branch"].toString("master");
#else
QString default_branch = obj["default_branch"].toString();
if ( default_branch.isEmpty () )
{
default_branch = "master";
}
#endif
std::string default_branch = (obj.contains("default_branch") && !obj["default_branch"].empty()) ? obj["default_branch"] : "master";
// Send branch list to anyone who is interested
emit gotBranchList(branches, default_branch);
@@ -231,20 +222,19 @@ void RemoteDatabase::gotReply(QNetworkReply* reply)
case RequestTypePush:
{
// Read and check results
QJsonDocument json = QJsonDocument::fromJson(reply->readAll());
if(json.isNull() || !json.isObject())
json obj = json::parse(reply->readAll(), nullptr, false);
if(obj.is_discarded() || !obj.is_object())
break;
QJsonObject obj = json.object();
// Create or update the record in our local checkout database
QString saveFileAs = localAdd(reply->url().fileName(), reply->property("certfile").toString(), obj["url"].toString(), obj["commit_id"].toString());
QString saveFileAs = localAdd(reply->url().fileName(), reply->property("certfile").toString(), QString::fromStdString(obj["url"]), obj["commit_id"]);
// If the name of the source file and the name we're saving as differ, we're doing an initial push. In this case, copy the source file to
// the destination path to avoid redownloading it when it's first used.
if(saveFileAs != reply->property("source_file").toString())
QFile::copy(reply->property("source_file").toString(), saveFileAs);
emit uploadFinished(obj["url"].toString());
emit uploadFinished(obj["url"]);
break;
}
}
@@ -480,7 +470,7 @@ void RemoteDatabase::push(const QString& filename, const QString& url, const QSt
addPart(multipart, "licence", licence);
addPart(multipart, "public", isPublic ? "true" : "false");
addPart(multipart, "branch", branch);
addPart(multipart, "commit", localLastCommitId(clientCert, url));
addPart(multipart, "commit", QString::fromStdString(localLastCommitId(clientCert, url)));
addPart(multipart, "force", forcePush ? "true" : "false");
addPart(multipart, "lastmodified", last_modified.toString("yyyy-MM-dd'T'HH:mm:ss'Z'"));
@@ -578,7 +568,7 @@ void RemoteDatabase::localAssureOpened()
}
}
QString RemoteDatabase::localAdd(QString filename, QString identity, const QUrl& url, const QString& new_commit_id)
QString RemoteDatabase::localAdd(QString filename, QString identity, const QUrl& url, const std::string& new_commit_id)
{
// This function adds a new local database clone to our internal list. It does so by adding a single
// new record to the remote dbs database. All the fields are extracted from the filename, the identity
@@ -595,8 +585,8 @@ QString RemoteDatabase::localAdd(QString filename, QString identity, const QUrl&
identity = f.fileName();
// Check if this file has already been checked in
QString last_commit_id = localLastCommitId(identity, url.toString());
if(last_commit_id.isNull())
std::string last_commit_id = localLastCommitId(identity, url.toString());
if(last_commit_id.empty())
{
// The file hasn't been checked in yet. So add a new record for it.
@@ -628,13 +618,13 @@ QString RemoteDatabase::localAdd(QString filename, QString identity, const QUrl&
return QString();
}
if(sqlite3_bind_text(stmt, 4, new_commit_id.toUtf8(), new_commit_id.toUtf8().length(), SQLITE_TRANSIENT))
if(sqlite3_bind_text(stmt, 4, new_commit_id.c_str(), static_cast<int>(new_commit_id.size()), SQLITE_TRANSIENT))
{
sqlite3_finalize(stmt);
return QString();
}
if(sqlite3_bind_text(stmt, 5, filename.toUtf8(), filename.toUtf8().length(), SQLITE_TRANSIENT))
if(sqlite3_bind_text(stmt, 5, filename.toUtf8(), filename.size(), SQLITE_TRANSIENT))
{
sqlite3_finalize(stmt);
return QString();
@@ -662,7 +652,7 @@ QString RemoteDatabase::localAdd(QString filename, QString identity, const QUrl&
if(sqlite3_prepare_v2(m_dbLocal, sql.toUtf8(), -1, &stmt, nullptr) != SQLITE_OK)
return QString();
if(sqlite3_bind_text(stmt, 1, new_commit_id.toUtf8(), new_commit_id.toUtf8().length(), SQLITE_TRANSIENT))
if(sqlite3_bind_text(stmt, 1, new_commit_id.c_str(), static_cast<int>(new_commit_id.size()), SQLITE_TRANSIENT))
{
sqlite3_finalize(stmt);
return QString();
@@ -825,7 +815,7 @@ QString RemoteDatabase::localCheckFile(const QString& local_file)
}
}
QString RemoteDatabase::localLastCommitId(QString identity, const QUrl& url)
std::string RemoteDatabase::localLastCommitId(QString identity, const QUrl& url)
{
// This function takes a file name and checks with which commit id we had checked out this file or last pushed it.
@@ -835,32 +825,32 @@ QString RemoteDatabase::localLastCommitId(QString identity, const QUrl& url)
QString sql = QString("SELECT commit_id FROM local WHERE identity=? AND url=?");
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(m_dbLocal, sql.toUtf8(), -1, &stmt, nullptr) != SQLITE_OK)
return QString();
return std::string();
QFileInfo f(identity); // Remove the path
identity = f.fileName();
if(sqlite3_bind_text(stmt, 1, identity.toUtf8(), identity.toUtf8().length(), SQLITE_TRANSIENT))
{
sqlite3_finalize(stmt);
return QString();
return std::string();
}
if(sqlite3_bind_text(stmt, 2, url.toString(QUrl::PrettyDecoded | QUrl::RemoveQuery).toUtf8(),
url.toString(QUrl::PrettyDecoded | QUrl::RemoveQuery).toUtf8().size(), SQLITE_TRANSIENT))
{
sqlite3_finalize(stmt);
return QString();
return std::string();
}
if(sqlite3_step(stmt) != SQLITE_ROW)
{
// If there was either an error or no record was found for this file name, stop here.
sqlite3_finalize(stmt);
return QString();
return std::string();
}
// Having come here we can assume that at least some local clone with the given file name
QString local_commit_id = QString::fromUtf8(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)));
std::string local_commit_id = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
sqlite3_finalize(stmt);
return local_commit_id;

View File

@@ -61,11 +61,11 @@ signals:
// a directory listing or the licence list.
void gotDirList(QString json, QVariant userdata);
void gotCurrentVersion(QString version, QString url);
void gotLicenceList(QMap<QString, QString> licences);
void gotBranchList(QStringList branches, QString default_branch);
void gotLicenceList(QMap<std::string, std::string> licences);
void gotBranchList(std::vector<std::string> branches, std::string default_branch);
// The uploadFinished() signal is emitted when a push() call is finished, i.e. a database upload has completed.
void uploadFinished(QString url);
void uploadFinished(std::string url);
private:
void gotEncrypted(QNetworkReply* reply);
@@ -77,10 +77,10 @@ private:
// Helper functions for managing the list of locally available databases
void localAssureOpened();
QString localAdd(QString filename, QString identity, const QUrl& url, const QString& new_commit_id);
QString localAdd(QString filename, QString identity, const QUrl& url, const std::string& new_commit_id);
QString localExists(const QUrl& url, QString identity);
QString localCheckFile(const QString& local_file);
QString localLastCommitId(QString clientCert, const QUrl& url);
std::string localLastCommitId(QString clientCert, const QUrl& url);
// Helper functions for building multi-part HTTP requests
void addPart(QHttpMultiPart* multipart, const QString& name, const QString& value);

View File

@@ -1,11 +1,10 @@
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QImage>
#include "RemoteModel.h"
#include "RemoteDatabase.h"
using json = nlohmann::json;
RemoteModelItem::RemoteModelItem(RemoteModelItem* parent) :
m_parent(parent),
m_fetchedDirectoryList(false)
@@ -65,25 +64,26 @@ void RemoteModelItem::setFetchedDirectoryList(bool fetched)
m_fetchedDirectoryList = fetched;
}
QList<RemoteModelItem*> RemoteModelItem::loadArray(const QJsonValue& value, RemoteModelItem* parent)
std::vector<RemoteModelItem*> RemoteModelItem::loadArray(const json& array, RemoteModelItem* parent)
{
QList<RemoteModelItem*> items;
std::vector<RemoteModelItem*> items;
// Loop through all directory items
QJsonArray array = value.toArray();
for(int i=0;i<array.size();i++)
for(const auto& elem : array)
{
// Create a new model item with the specified parent
RemoteModelItem* item = new RemoteModelItem(parent);
// Save all relevant values. If one of the values isn't set in the JSON document, an empty string
// will be stored
item->setValue(RemoteModelColumnName, array.at(i).toObject().value("name"));
item->setValue(RemoteModelColumnType, array.at(i).toObject().value("type"));
item->setValue(RemoteModelColumnUrl, array.at(i).toObject().value("url"));
item->setValue(RemoteModelColumnCommitId, array.at(i).toObject().value("commit_id"));
item->setValue(RemoteModelColumnSize, array.at(i).toObject().value("size"));
item->setValue(RemoteModelColumnLastModified, array.at(i).toObject().value("last_modified"));
item->setValue(RemoteModelColumnName, QString::fromStdString(elem["name"]));
item->setValue(RemoteModelColumnType, QString::fromStdString(elem["type"]));
item->setValue(RemoteModelColumnUrl, QString::fromStdString(elem["url"]));
item->setValue(RemoteModelColumnLastModified, QString::fromStdString(elem["last_modified"]));
if(elem.contains("commit_id"))
item->setValue(RemoteModelColumnCommitId, QString::fromStdString(elem["commit_id"]));
if(elem.contains("size"))
item->setValue(RemoteModelColumnSize, QString::number(static_cast<int>(elem["size"])));
items.push_back(item);
}
@@ -121,13 +121,12 @@ void RemoteModel::setNewRootDir(const QString& url, const QString& cert)
remoteDatabase.fetch(currentRootDirectory, RemoteDatabase::RequestTypeDirectory, currentClientCert, QModelIndex());
}
void RemoteModel::parseDirectoryListing(const QString& json, const QVariant& userdata)
void RemoteModel::parseDirectoryListing(const QString& text, const QVariant& userdata)
{
// Load new JSON root document assuming it's an array
QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8());
if(doc.isNull() || !doc.isArray())
json array = json::parse(text.toStdString(), nullptr, false);
if(array.is_discarded() || !array.is_array())
return;
QJsonArray array = doc.array();
// Get model index to store the new data under
QModelIndex parent = userdata.toModelIndex();
@@ -148,8 +147,8 @@ void RemoteModel::parseDirectoryListing(const QString& json, const QVariant& use
}
// Insert data
beginInsertRows(parent, 0, array.size());
QList<RemoteModelItem*> items = RemoteModelItem::loadArray(QJsonValue(array), parentItem);
beginInsertRows(parent, 0, static_cast<int>(array.size()));
std::vector<RemoteModelItem*> items = RemoteModelItem::loadArray(array, parentItem);
for(RemoteModelItem* item : items)
parentItem->appendChild(item);
endInsertRows();

View File

@@ -4,6 +4,8 @@
#include <QAbstractItemModel>
#include <QStringList>
#include <json.hpp>
class RemoteDatabase;
// List of fields stored in the JSON data
@@ -39,7 +41,7 @@ public:
// This function assumes the JSON value it's getting passed is an array ("[{...}, {...}, {...}, ...]"). It returns a list of model items, one
// per array entry and each with the specified parent set.
static QList<RemoteModelItem*> loadArray(const QJsonValue& value, RemoteModelItem* parent = nullptr);
static std::vector<RemoteModelItem*> loadArray(const nlohmann::json& array, RemoteModelItem* parent = nullptr);
private:
// These are just the fields from the json objects returned by the dbhub.io server
@@ -93,7 +95,7 @@ signals:
private slots:
// This is called whenever a network reply containing a directory listing arrives. json contains the reply data, userdata
// contains some custom data passed to the request. In this case we expect this to be the model index of the parent tree item.
void parseDirectoryListing(const QString& json, const QVariant& userdata);
void parseDirectoryListing(const QString& text, const QVariant& userdata);
private:
// Pointer to the root item. This contains all the actual item data.

View File

@@ -104,7 +104,7 @@ bool RemotePushDialog::forcePush() const
return ui->checkForce->isChecked();
}
void RemotePushDialog::fillInLicences(const QMap<QString, QString>& licences)
void RemotePushDialog::fillInLicences(const QMap<std::string, std::string>& licences)
{
// Clear licence list and add default item for unspecified licence
ui->comboLicence->clear();
@@ -112,20 +112,20 @@ void RemotePushDialog::fillInLicences(const QMap<QString, QString>& licences)
// Parse licence list and fill combo box. Show the full name to the user and use the short name as user data.
for(auto it=licences.constBegin();it!=licences.constEnd();++it)
ui->comboLicence->addItem(it.value(), it.key());
ui->comboLicence->addItem(QString::fromStdString(it.value()), QString::fromStdString(it.key()));
}
void RemotePushDialog::fillInBranches(const QStringList& branches, const QString& default_branch)
void RemotePushDialog::fillInBranches(const std::vector<std::string>& branches, const std::string& default_branch)
{
// Clear branch list and add the default branch
ui->comboBranch->clear();
ui->comboBranch->addItem(default_branch);
ui->comboBranch->addItem(QString::fromStdString(default_branch));
// Add rest of the branch list to the combo box
for(const QString& branch : branches)
for(const std::string& branch : branches)
{
if(branch != default_branch)
ui->comboBranch->addItem(branch);
ui->comboBranch->addItem(QString::fromStdString(branch));
}
}

View File

@@ -45,8 +45,8 @@ protected slots:
void reloadBranchList();
void fillInLicences(const QMap<QString, QString>& licences);
void fillInBranches(const QStringList& branches, const QString& default_branch);
void fillInLicences(const QMap<std::string, std::string>& licences);
void fillInBranches(const std::vector<std::string>& branches, const std::string& default_branch);
};
#endif