520 lines
15 KiB
C++
520 lines
15 KiB
C++
|
|
|
|
#pragma once
|
|
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonParseError>
|
|
#include <QJsonValue>
|
|
#include <QMap>
|
|
#include <QString>
|
|
#include <QVector>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace LtsUtils {
|
|
|
|
class JsonParseError : public std::runtime_error {
|
|
public:
|
|
JsonParseError(const QJsonParseError &err)
|
|
: std::runtime_error(
|
|
"json parse error: " + err.errorString().toStdString() +
|
|
" - at offset " + std::to_string(err.offset)) {}
|
|
};
|
|
|
|
class JsonError : public std::runtime_error {
|
|
public:
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
|
|
class JsonKeyError : public JsonError {
|
|
public:
|
|
using JsonError::JsonError;
|
|
};
|
|
|
|
class JsonRangeError : public JsonError {
|
|
public:
|
|
using JsonError::JsonError;
|
|
};
|
|
|
|
class JsonTypeError : public JsonError {
|
|
public:
|
|
using JsonError::JsonError;
|
|
};
|
|
|
|
class JsonObject {
|
|
public:
|
|
class Key;
|
|
|
|
JsonObject() = default;
|
|
explicit JsonObject(const QJsonObject &json)
|
|
: m_json(json), m_jsonIsSet(true) {}
|
|
|
|
explicit JsonObject(QJsonObject &&json)
|
|
: m_json(std::move(json)), m_jsonIsSet(true) {}
|
|
|
|
static inline JsonObject fromJson(const char *json, int len = -1);
|
|
static inline JsonObject fromJson(const QByteArray &json) {
|
|
return fromJson(json.data(), json.size());
|
|
}
|
|
static inline JsonObject fromJson(const std::string &json) {
|
|
return fromJson(json.c_str(), json.size());
|
|
}
|
|
|
|
static inline JsonObject fromJsonSafe(const char *json,
|
|
int len = -1) noexcept(false);
|
|
static inline JsonObject
|
|
fromJsonSafe(const QByteArray &json) noexcept(false) {
|
|
return fromJsonSafe(json.data(), json.size());
|
|
}
|
|
static inline JsonObject
|
|
fromJsonSafe(const std::string &json) noexcept(false) {
|
|
return fromJsonSafe(json.c_str(), json.size());
|
|
}
|
|
|
|
template <class T> [[nodiscard]] bool contains(const Key &key) const;
|
|
|
|
[[nodiscard]] bool isNull(const Key &key) const {
|
|
return contains<void>(key);
|
|
}
|
|
|
|
[[nodiscard]] bool isEmpty() const { return m_json.isEmpty(); }
|
|
[[nodiscard]] bool isValid() const { return m_jsonIsSet; }
|
|
|
|
template <class T> T value(const Key &key) const noexcept(false);
|
|
|
|
template <class T>
|
|
T valueOr(const Key &key, const T &defaultValue) const noexcept(false);
|
|
|
|
template <class T> [[nodiscard]] bool isMap() const;
|
|
|
|
template <class T> QMap<QString, T> toMap() const noexcept(false);
|
|
|
|
template <class T> std::map<std::string, T> toStdMap() const;
|
|
|
|
bool operator==(const JsonObject &other) const {
|
|
return m_json == other.m_json;
|
|
}
|
|
bool operator!=(const JsonObject &other) const { return !(*this == other); }
|
|
|
|
class Key {
|
|
public:
|
|
Key(QString s) : m_s(std::move(s)) {}
|
|
|
|
Key(const std::string &s) : m_s(QString::fromStdString(s)) {}
|
|
|
|
Key(const char *s) : m_s(QString::fromUtf8(s)) {}
|
|
|
|
operator const QString &() const { return m_s; }
|
|
|
|
private:
|
|
const QString m_s;
|
|
};
|
|
|
|
operator const QJsonObject &() const { return m_json; }
|
|
[[nodiscard]] const QJsonObject &object() const { return m_json; }
|
|
|
|
private:
|
|
[[nodiscard]] bool containsAny(const QString &key) const {
|
|
return m_json.contains(key);
|
|
}
|
|
|
|
QJsonObject m_json;
|
|
bool m_jsonIsSet = false;
|
|
};
|
|
|
|
JsonObject JsonObject::fromJson(const char *json, int len) {
|
|
if (len < 0)
|
|
len = strlen(json);
|
|
QJsonParseError error;
|
|
const auto doc =
|
|
QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
|
|
if (error.error != QJsonParseError::NoError)
|
|
return {};
|
|
|
|
if (!doc.isObject())
|
|
return {};
|
|
|
|
return JsonObject(doc.object());
|
|
}
|
|
|
|
JsonObject JsonObject::fromJsonSafe(const char *json, int len) noexcept(false) {
|
|
if (len < 0)
|
|
len = strlen(json);
|
|
QJsonParseError error;
|
|
const auto doc =
|
|
QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
|
|
if (error.error != QJsonParseError::NoError)
|
|
throw JsonParseError(error);
|
|
|
|
if (!doc.isObject())
|
|
throw JsonTypeError("JSON root is not an object");
|
|
|
|
return JsonObject(doc.object());
|
|
}
|
|
|
|
class JsonArray {
|
|
public:
|
|
JsonArray() = default;
|
|
explicit JsonArray(const QJsonArray &json)
|
|
: m_json(json), m_jsonIsSet(true) {}
|
|
|
|
explicit JsonArray(QJsonArray &&json)
|
|
: m_json(std::move(json)), m_jsonIsSet(true) {}
|
|
|
|
static inline JsonArray fromJson(const char *json, int len = -1);
|
|
static inline JsonArray fromJson(const QByteArray &json) {
|
|
return fromJson(json.data(), json.size());
|
|
}
|
|
static inline JsonArray fromJson(const std::string &json) {
|
|
return fromJson(json.c_str(), json.size());
|
|
}
|
|
|
|
static inline JsonArray fromJsonSafe(const char *json,
|
|
int len = -1) noexcept(false);
|
|
static inline JsonArray fromJsonSafe(const QByteArray &json) noexcept(false) {
|
|
return fromJson(json.data(), json.size());
|
|
}
|
|
static inline JsonArray
|
|
fromJsonSafe(const std::string &json) noexcept(false) {
|
|
return fromJson(json.c_str(), json.size());
|
|
}
|
|
|
|
[[nodiscard]] int size() const { return m_json.size(); }
|
|
[[nodiscard]] bool isEmpty() const { return m_json.isEmpty(); }
|
|
[[nodiscard]] bool isValid() const { return m_jsonIsSet; }
|
|
|
|
template <class T>
|
|
[[nodiscard]] bool contains(int index) const noexcept(false);
|
|
|
|
[[nodiscard]] bool isNull(int index) const noexcept(false) {
|
|
return contains<void>(index);
|
|
}
|
|
|
|
template <class T> T at(int index) const noexcept(false);
|
|
|
|
template <class T> [[nodiscard]] bool isVector() const;
|
|
|
|
template <class T> QVector<T> toVector() const noexcept(false);
|
|
|
|
template <class T> std::vector<T> toStdVector() const;
|
|
|
|
bool operator==(const JsonArray &other) const {
|
|
return m_json == other.m_json;
|
|
}
|
|
bool operator!=(const JsonArray &other) const { return !(*this == other); }
|
|
|
|
const QJsonArray &operator()() const { return m_json; }
|
|
|
|
private:
|
|
QJsonArray m_json;
|
|
bool m_jsonIsSet = false;
|
|
};
|
|
|
|
JsonArray JsonArray::fromJson(const char *json, int len) {
|
|
if (len < 0)
|
|
len = strlen(json);
|
|
QJsonParseError error;
|
|
QJsonDocument doc =
|
|
QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
|
|
if (error.error != QJsonParseError::NoError)
|
|
return {};
|
|
|
|
if (!doc.isArray())
|
|
return {};
|
|
|
|
return JsonArray(doc.array());
|
|
}
|
|
|
|
JsonArray JsonArray::fromJsonSafe(const char *json, int len) noexcept(false) {
|
|
if (len < 0)
|
|
len = strlen(json);
|
|
QJsonParseError error;
|
|
const auto doc =
|
|
QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
|
|
if (error.error != QJsonParseError::NoError)
|
|
throw JsonParseError(error);
|
|
|
|
if (!doc.isArray())
|
|
throw JsonTypeError("JSON root is not an array");
|
|
|
|
return JsonArray(doc.array());
|
|
}
|
|
|
|
template <class T> struct JsonValue;
|
|
|
|
template <class Int> struct JsonLimits {
|
|
static constexpr Int lowest() { return std::numeric_limits<Int>::lowest(); }
|
|
static constexpr Int max() { return std::numeric_limits<Int>::max(); }
|
|
};
|
|
|
|
template <> struct JsonLimits<int64_t> {
|
|
static constexpr int64_t lowest() {
|
|
return -(static_cast<int64_t>(1) << 53) + 1;
|
|
}
|
|
static constexpr int64_t max() { return (static_cast<int64_t>(1) << 53) - 1; }
|
|
};
|
|
|
|
template <> struct JsonLimits<uint64_t> {
|
|
static constexpr uint64_t lowest() { return 0; }
|
|
static constexpr uint64_t max() { return JsonLimits<int64_t>::max(); }
|
|
};
|
|
|
|
template <class Int> struct JsonIntValue {
|
|
static bool matchesType(const QJsonValue &value) {
|
|
if (!value.isDouble())
|
|
return false;
|
|
double val = value.toDouble();
|
|
|
|
if (val < JsonLimits<Int>::lowest())
|
|
return false;
|
|
if (val > JsonLimits<Int>::max())
|
|
return false;
|
|
|
|
double wholeInt = static_cast<Int>(val);
|
|
return val == wholeInt;
|
|
}
|
|
|
|
static Int value(const QJsonValue &value) noexcept(false) {
|
|
if (!matchesType(value))
|
|
throw JsonTypeError("JSON value not of type Int");
|
|
return static_cast<Int>(value.toDouble());
|
|
}
|
|
};
|
|
|
|
template <> struct JsonValue<int16_t> : JsonIntValue<int16_t> {};
|
|
|
|
template <> struct JsonValue<uint16_t> : JsonIntValue<uint16_t> {};
|
|
|
|
template <> struct JsonValue<int32_t> : JsonIntValue<int32_t> {};
|
|
|
|
template <> struct JsonValue<uint32_t> : JsonIntValue<uint32_t> {};
|
|
|
|
template <> struct JsonValue<int64_t> : JsonIntValue<int64_t> {};
|
|
|
|
template <> struct JsonValue<uint64_t> : JsonIntValue<uint64_t> {};
|
|
|
|
template <> struct JsonValue<double> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isDouble(); }
|
|
|
|
static double value(const QJsonValue &value) noexcept(false) {
|
|
if (!matchesType(value))
|
|
throw JsonTypeError("JSON value not of type Double");
|
|
return value.toDouble();
|
|
}
|
|
};
|
|
|
|
template <> struct JsonValue<bool> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isBool(); }
|
|
|
|
static bool value(const QJsonValue &value) noexcept(false) {
|
|
if (!matchesType(value))
|
|
throw JsonTypeError("JSON value not of type Bool");
|
|
return value.toBool();
|
|
}
|
|
};
|
|
|
|
template <> struct JsonValue<QString> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isString(); }
|
|
|
|
static QString value(const QJsonValue &value) noexcept(false) {
|
|
if (!matchesType(value))
|
|
throw JsonTypeError("JSON value not of type String");
|
|
return value.toString();
|
|
}
|
|
};
|
|
|
|
template <> struct JsonValue<std::string> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isString(); }
|
|
|
|
static std::string value(const QJsonValue &value) {
|
|
return JsonValue<QString>::value(value).toStdString();
|
|
}
|
|
};
|
|
|
|
template <> struct JsonValue<QJsonObject> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isObject(); }
|
|
|
|
static QJsonObject value(const QJsonValue &value) noexcept(false) {
|
|
if (!matchesType(value))
|
|
throw JsonTypeError("JSON value not of type Object");
|
|
return value.toObject();
|
|
}
|
|
};
|
|
|
|
template <> struct JsonValue<JsonObject> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isObject(); }
|
|
static JsonObject value(const QJsonValue &value) {
|
|
return JsonObject(JsonValue<QJsonObject>::value(value));
|
|
}
|
|
};
|
|
|
|
template <> struct JsonValue<void> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isNull(); }
|
|
};
|
|
|
|
template <> struct JsonValue<QJsonArray> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isArray(); }
|
|
|
|
static QJsonArray value(const QJsonValue &value) noexcept(false) {
|
|
if (!matchesType(value))
|
|
throw JsonTypeError("JSON value not of type Array");
|
|
return value.toArray();
|
|
}
|
|
};
|
|
|
|
template <> struct JsonValue<JsonArray> {
|
|
static bool matchesType(const QJsonValue &value) { return value.isArray(); }
|
|
static JsonArray value(const QJsonValue &value) {
|
|
return JsonArray(JsonValue<QJsonArray>::value(value));
|
|
}
|
|
};
|
|
|
|
template <class T> struct JsonValue<QVector<T>> {
|
|
static bool matchesType(const QJsonValue &value) {
|
|
if (!value.isArray())
|
|
return false;
|
|
|
|
const JsonArray array(value.toArray());
|
|
return array.isVector<T>();
|
|
}
|
|
|
|
static QVector<T> value(const QJsonValue &value) {
|
|
const JsonArray array(JsonValue<QJsonArray>::value(value));
|
|
return array.toVector<T>();
|
|
}
|
|
};
|
|
|
|
template <class T> struct JsonValue<std::vector<T>> {
|
|
static bool matchesType(const QJsonValue &value) {
|
|
return JsonValue<QVector<T>>::matchesType(value);
|
|
}
|
|
|
|
static std::vector<T> value(const QJsonValue &value) {
|
|
const JsonArray array(JsonValue<QJsonArray>::value(value));
|
|
return array.toStdVector<T>();
|
|
}
|
|
};
|
|
|
|
template <class T> struct JsonValue<QMap<QString, T>> {
|
|
static bool matchesType(const QJsonValue &value) {
|
|
if (!value.isObject())
|
|
return false;
|
|
|
|
const JsonObject obj(JsonValue<QJsonObject>::value(value));
|
|
return obj.isMap<T>();
|
|
}
|
|
|
|
static QMap<QString, T> value(const QJsonValue &value) {
|
|
const JsonObject obj(JsonValue<QJsonObject>::value(value));
|
|
return obj.toMap<T>();
|
|
}
|
|
};
|
|
|
|
template <class T> struct JsonValue<std::map<std::string, T>> {
|
|
static bool matchesType(const QJsonValue &value) {
|
|
return JsonValue<QMap<QString, T>>::matchesType(value);
|
|
}
|
|
|
|
static std::map<std::string, T> value(const QJsonValue &value) {
|
|
const JsonObject obj(JsonValue<QJsonObject>::value(value));
|
|
return obj.toStdMap<T>();
|
|
}
|
|
};
|
|
|
|
template <class T> bool JsonObject::contains(const Key &key) const {
|
|
if (!m_json.contains(key))
|
|
return false;
|
|
return JsonValue<T>::matchesType(m_json.value(key));
|
|
}
|
|
|
|
template <class T> T JsonObject::value(const Key &key) const noexcept(false) {
|
|
if (!containsAny(key))
|
|
throw JsonKeyError("no element with key: " +
|
|
static_cast<const QString &>(key).toStdString());
|
|
try {
|
|
return JsonValue<T>::value(m_json.value(key));
|
|
} catch (const JsonTypeError &typeError) {
|
|
throw JsonTypeError(std::string(typeError.what()) + " - key: " +
|
|
static_cast<const QString &>(key).toStdString());
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
T JsonObject::valueOr(const Key &key, const T &defaultValue) const {
|
|
try {
|
|
return value<T>(key);
|
|
}
|
|
|
|
catch (const JsonKeyError &) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
template <class T> bool JsonObject::isMap() const {
|
|
const QStringList keys = m_json.keys();
|
|
for (const auto &key : keys)
|
|
if (!JsonValue<T>::matchesType(m_json.value(key)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class T> QMap<QString, T> JsonObject::toMap() const {
|
|
QMap<QString, T> result;
|
|
for (auto current = m_json.begin(); current != m_json.end(); ++current)
|
|
result.insert(current.key(), JsonValue<T>::value(current.value()));
|
|
return result;
|
|
}
|
|
|
|
template <class T> std::map<std::string, T> JsonObject::toStdMap() const {
|
|
std::map<std::string, T> result;
|
|
for (auto current = m_json.begin(); current != m_json.end(); ++current)
|
|
result.insert(
|
|
{current.key().toStdString(), JsonValue<T>::value(current.value())});
|
|
return result;
|
|
}
|
|
|
|
template <class T> bool JsonArray::contains(int index) const noexcept(false) {
|
|
if (index < 0 || index >= m_json.size())
|
|
throw JsonRangeError("index out of range: " + std::to_string(index));
|
|
return JsonValue<T>::matchesType(m_json.at(index));
|
|
}
|
|
|
|
template <class T> T JsonArray::at(int index) const noexcept(false) {
|
|
if (index < 0 || index >= m_json.size())
|
|
throw JsonRangeError("index out of range: " + std::to_string(index));
|
|
return JsonValue<T>::value(m_json.at(index));
|
|
}
|
|
|
|
template <class T> bool JsonArray::isVector() const {
|
|
for (const QJsonValue &value : m_json)
|
|
if (!JsonValue<T>::matchesType(value))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
template <class T> QVector<T> JsonArray::toVector() const {
|
|
QVector<T> result;
|
|
result.reserve(m_json.size());
|
|
for (const QJsonValue &value : m_json)
|
|
result.push_back(JsonValue<T>::value(value));
|
|
return result;
|
|
}
|
|
|
|
template <class T> std::vector<T> JsonArray::toStdVector() const {
|
|
std::vector<T> result;
|
|
result.reserve(m_json.size());
|
|
for (const QJsonValue &value : m_json)
|
|
result.push_back(JsonValue<T>::value(value));
|
|
return result;
|
|
}
|
|
|
|
} // namespace LtsUtils
|