/** * @RDE_HEADER@ * JsonObject * $Id$ * $HeadURL$ * $Author$ * Created by Christian Fröbel, SLSE, 2017-10-26 */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }; /** * A Wrapper for QJsonObject for typesafe, explicit and strict access. * @author Christian Fröbel */ class JsonObject { public: /** * Just a small helper class we use for the key which can * be implicitly created from a QString, std::string, const char*. * Encoding of std::string and const char* is assumed to be utf-8. * Should not be used explicitly be the user. */ 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) {} // JsonObject(JsonObject &&json) // : m_json(std::move(json.m_json)) // , m_jsonIsSet(true) // {} /** * create a JsonObject out of some data, returns an empty array if it could not be parsed */ ///@{ 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()); } ///@} /** * @return true if the JsonObject contains an element with that name and that element is of type T. * @see value() for allowed types for T. */ template bool contains(const Key &key) const; bool isNull(const Key &key) const { return contains(key); } bool isEmpty() const { return m_json.isEmpty(); } bool isValid() const { return m_jsonIsSet; } /** * Retrieve a value of a given type T. * T can be an integer type (unsigned/signed 16-, 32-, 64-bit), * double, bool, QString, std::string, QJsonObject, JsonObject, QJsonArray, JsonArray, * QVector, std::vector, QMap and std::map * where T is one of the given types (recursively). * @throw JsonKeyError if an element with that key does not exist. * @throw JsonTypeError if the requested type of the element does not match. */ template T value(const Key &key) const noexcept(false); /** * Same as value() but return a default value if no element with that key exists. * @throw JsonTypeError if an element with that key exists but has a non-macthing type. */ template T valueOr(const Key &key, const T &defaultValue) const noexcept(false); /** * Check whether all values of this JsonObject are of type T. * If true, toMap()/toStdMap() will succeed. */ template bool isMap() const; /** * Convert this JsonObject into a map with values of type T. * @throw JsonTypeError if some value is not of type T. * @see value() for allowed types of T. */ template QMap toMap() const noexcept(false); /** Same as toMap(). */ template std::map 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(const QString &s) : m_s(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; } const QJsonObject &object() const { return m_json; } private: 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 JsonObject(); if (!doc.isObject()) return JsonObject(); 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()); } //============================================================================== /** * A Wrapper for QJsonArray for typesafe, explicit and strict access. * @author Christian Fröbel */ 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) {} // JsonArray(JsonArray &&json) // : m_json(std::move(json.m_json)) // , m_jsonIsSet(true) // {} // JsonArray& operator=(JsonArray&& json) // { // m_json = std::move(json.m_json); // m_jsonIsSet = json.m_jsonIsSet; // return *this; // } /** * create a JsonArray out of some data, returns an empty array if it could not be parsed */ ///@{ 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()); } ///@} int size() const { return m_json.size(); } bool isEmpty() const { return m_json.isEmpty(); } bool isValid() const { return m_jsonIsSet; } /** @throw JsonRangeError if index is out of range. */ template bool contains(int index) const noexcept(false); /** @throw JsonRangeError if index is out of range. */ bool isNull(int index) const noexcept(false) { return contains(index); } /** @throw JsonRangeError if index is out of range. */ template T at(int index) const noexcept(false); /** * Check whether all values of this JsonArray are of type T. * If true, toVector()/toStdVector() will succeed. */ template bool isVector() const; /** * Convert this JsonArray into a vector with values of type T. * @throw JsonTypeError if some value is not of type T. */ template QVector toVector() const noexcept(false); template std::vector 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 JsonArray(); if (!doc.isArray()) return JsonArray(); 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()); } //============================================================================== /** * Primary template without implementation. */ template struct JsonValue; /** * Limits used by JsonIntValue. * Defaults to std::numeric_limits, except for int64_t and uint64_t; see below. */ template struct JsonLimits { static constexpr Int lowest() { return std::numeric_limits::lowest(); } static constexpr Int max() { return std::numeric_limits::max(); } }; /** * Specialization for int64_t. * As a JSON number is stored in a 64-bit double, maximum accuracy * is limited by bit-width of mantissa (52 bit). * @see RFC 7159, IEEE 754-2008 */ template<> struct JsonLimits { static constexpr int64_t lowest() { return -(static_cast(1) << 53) + 1; } static constexpr int64_t max() { return (static_cast(1) << 53) - 1; } }; /** * Specialization for uint64_t. */ template<> struct JsonLimits { static constexpr uint64_t lowest() { return 0; } static constexpr uint64_t max() { return JsonLimits::max(); } }; /** * Implementation for integer values. */ template struct JsonIntValue { static bool matchesType(const QJsonValue &value) { if (!value.isDouble()) return false; double val = value.toDouble(); if (val < JsonLimits::lowest()) return false; if (val > JsonLimits::max()) return false; double wholeInt = static_cast(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(value.toDouble()); } }; /** * Specializations for Integers. */ ///@{ template<> struct JsonValue : JsonIntValue {}; template<> struct JsonValue : JsonIntValue {}; template<> struct JsonValue : JsonIntValue {}; template<> struct JsonValue : JsonIntValue {}; template<> struct JsonValue : JsonIntValue {}; template<> struct JsonValue : JsonIntValue {}; ///@} /** * Specialization for double. */ template<> struct JsonValue { 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(); } }; /** * Specialization for bool. */ template<> struct JsonValue { 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(); } }; /** * Specialization for QString. */ template<> struct JsonValue { 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(); } }; /** * Specialization for std::string. */ template<> struct JsonValue { static bool matchesType(const QJsonValue &value) { return value.isString(); } static std::string value(const QJsonValue &value) { return JsonValue::value(value).toStdString(); } }; /** * Specialization for QJsonObject. */ template<> struct JsonValue { 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(); } }; /** * Specialization for JsonObject. */ template<> struct JsonValue { static bool matchesType(const QJsonValue &value) { return value.isObject(); } static JsonObject value(const QJsonValue &value) { return JsonObject(JsonValue::value(value)); } }; /** * Specialization for Null. */ template<> struct JsonValue { static bool matchesType(const QJsonValue &value) { return value.isNull(); } }; /** * Specialization for QJsonArray. */ template<> struct JsonValue { 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(); } }; /** * Specialization for JsonArray. */ template<> struct JsonValue { static bool matchesType(const QJsonValue &value) { return value.isArray(); } static JsonArray value(const QJsonValue &value) { return JsonArray(JsonValue::value(value)); } }; /** * Partial specialisation for QVector. */ template struct JsonValue> { static bool matchesType(const QJsonValue &value) { if (!value.isArray()) return false; const JsonArray array(value.toArray()); return array.isVector(); } static QVector value(const QJsonValue &value) { const JsonArray array(JsonValue::value(value)); return array.toVector(); } }; /** * Partial specialisation for std::vector. */ template struct JsonValue> { static bool matchesType(const QJsonValue &value) { return JsonValue>::matchesType(value); } static std::vector value(const QJsonValue &value) { const JsonArray array(JsonValue::value(value)); return array.toStdVector(); } }; /** * Partial specialisation for QMap. */ template struct JsonValue> { static bool matchesType(const QJsonValue &value) { if (!value.isObject()) return false; const JsonObject obj(JsonValue::value(value)); return obj.isMap(); } static QMap value(const QJsonValue &value) { const JsonObject obj(JsonValue::value(value)); return obj.toMap(); } }; /** * Partial specialisation for std::map. */ template struct JsonValue> { static bool matchesType(const QJsonValue &value) { return JsonValue>::matchesType(value); } static std::map value(const QJsonValue &value) { const JsonObject obj(JsonValue::value(value)); return obj.toStdMap(); } }; //============================================================================== template bool JsonObject::contains(const Key &key) const { if (!m_json.contains(key)) return false; return JsonValue::matchesType(m_json.value(key)); } template T JsonObject::value(const Key &key) const noexcept(false) { if (!containsAny(key)) throw JsonKeyError("no element with key: " + static_cast(key).toStdString()); try { return JsonValue::value(m_json.value(key)); } catch(const JsonTypeError &typeError) { throw JsonTypeError(std::string(typeError.what()) + " - key: " + static_cast(key).toStdString()); } } template T JsonObject::valueOr(const Key &key, const T &defaultValue) const { try { return value(key); } // Only catch JsonKeyError, JsonTypeError shall be propagated. catch (const JsonKeyError &) { return defaultValue; } } template bool JsonObject::isMap() const { const QStringList keys = m_json.keys(); for (const auto &key : keys) if (!JsonValue::matchesType(m_json.value(key))) return false; return true; } template QMap JsonObject::toMap() const { QMap result; for (auto current = m_json.begin(); current != m_json.end(); ++current) result.insert(current.key(), JsonValue::value(current.value())); return result; } template std::map JsonObject::toStdMap() const { std::map result; for (auto current = m_json.begin(); current != m_json.end(); ++current) result.insert({current.key().toStdString(), JsonValue::value(current.value())}); return result; } //============================================================================== template 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::matchesType(m_json.at(index)); } template 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::value(m_json.at(index)); } template bool JsonArray::isVector() const { for (const QJsonValue &value : m_json) if (!JsonValue::matchesType(value)) return false; return true; } template QVector JsonArray::toVector() const { QVector result; result.reserve(m_json.size()); for (const QJsonValue &value : m_json) result.push_back(JsonValue::value(value)); return result; } template std::vector JsonArray::toStdVector() const { std::vector result; result.reserve(m_json.size()); for (const QJsonValue &value : m_json) result.push_back(JsonValue::value(value)); return result; } } // namespace LtsUtils