Files
qt5-legatus-legacy/Lts/Common/LtsUtils/LtsUtilsCore/json_object.h
T
2026-08-13 18:33:10 +02:00

756 lines
19 KiB
C++
Executable File

/**
* @RDE_HEADER@
* JsonObject
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Fröbel, SLSE, 2017-10-26
*/
#pragma once
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QVector>
#include <QMap>
#include <QString>
#include <string>
#include <vector>
#include <map>
#include <exception>
#include <cstdint>
#include <limits>
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<class T>
bool contains(const Key &key) const;
bool isNull(const Key &key) const { return contains<void>(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<T>, std::vector<T>, QMap<QString, T> and std::map<std::string, T>
* 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<class T>
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<class T>
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<class T>
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<class T>
QMap<QString, T> toMap() const noexcept(false);
/** Same as toMap(). */
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(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<class T>
bool contains(int index) const noexcept(false);
/** @throw JsonRangeError if index is out of range. */
bool isNull(int index) const noexcept(false) { return contains<void>(index); }
/** @throw JsonRangeError if index is out of range. */
template<class T>
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<class T>
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<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 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<class T>
struct JsonValue;
/**
* Limits used by JsonIntValue.
* Defaults to std::numeric_limits, except for int64_t and uint64_t; see below.
*/
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(); }
};
/**
* 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<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; }
};
/**
* Specialization for uint64_t.
*/
template<>
struct JsonLimits<uint64_t>
{
static constexpr uint64_t lowest() { return 0; }
static constexpr uint64_t max() { return JsonLimits<int64_t>::max(); }
};
/**
* Implementation for integer values.
*/
template<class Int/*, const char *typeName*/>
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());
}
};
/**
* Specializations for Integers.
*/
///@{
template<>
struct JsonValue<int16_t> : JsonIntValue<int16_t/*, TYPE_NAME_INT16*/>
{};
template<>
struct JsonValue<uint16_t> : JsonIntValue<uint16_t/*, TYPE_NAME_UINT16*/>
{};
template<>
struct JsonValue<int32_t> : JsonIntValue<int32_t/*, TYPE_NAME_INT32*/>
{};
template<>
struct JsonValue<uint32_t> : JsonIntValue<uint32_t/*, TYPE_NAME_UINT32*/>
{};
template<>
struct JsonValue<int64_t> : JsonIntValue<int64_t/*, TYPE_NAME_INT64*/>
{};
template<>
struct JsonValue<uint64_t> : JsonIntValue<uint64_t/*, TYPE_NAME_UINT64*/>
{};
///@}
/**
* Specialization for double.
*/
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();
}
};
/**
* Specialization for bool.
*/
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();
}
};
/**
* Specialization for QString.
*/
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();
}
};
/**
* Specialization for std::string.
*/
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();
}
};
/**
* Specialization for QJsonObject.
*/
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();
}
};
/**
* Specialization for JsonObject.
*/
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)); }
};
/**
* Specialization for Null.
*/
template<>
struct JsonValue<void>
{
static bool matchesType(const QJsonValue &value) { return value.isNull(); }
};
/**
* Specialization for QJsonArray.
*/
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();
}
};
/**
* Specialization for JsonArray.
*/
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)); }
};
/**
* Partial specialisation for QVector<T>.
*/
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>();
}
};
/**
* Partial specialisation for std::vector<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>();
}
};
/**
* Partial specialisation for QMap<QString, 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>();
}
};
/**
* Partial specialisation for std::map<std::string, 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);
}
// Only catch JsonKeyError, JsonTypeError shall be propagated.
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