// SPDX-FileCopyrightText: 2017 Kitsune Ral // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "util.h" #include #include // Includes #include #include #include #include #include #include class QVariant; namespace Quotient { template struct JsonObjectConverter { static void dumpTo(QJsonObject& jo, const T& pod) { jo = pod.toJson(); } static void fillFrom(const QJsonObject& jo, T& pod) { pod = T(jo); } }; template struct JsonConverter { static QJsonObject dump(const T& pod) { QJsonObject jo; JsonObjectConverter::dumpTo(jo, pod); return jo; } static T doLoad(const QJsonObject& jo) { T pod; JsonObjectConverter::fillFrom(jo, pod); return pod; } static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); } static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } }; template inline auto toJson(const T& pod) { return JsonConverter::dump(pod); } inline auto toJson(const QJsonObject& jo) { return jo; } template inline void fillJson(QJsonObject& json, const T& data) { JsonObjectConverter::dumpTo(json, data); } template inline T fromJson(const QJsonValue& jv) { return JsonConverter::load(jv); } template inline T fromJson(const QJsonDocument& jd) { return JsonConverter::load(jd); } // Convenience fromJson() overloads that deduce T instead of requiring // the coder to explicitly type it. They still enforce the // overwrite-everything semantics of fromJson(), unlike fillFromJson() template inline void fromJson(const QJsonValue& jv, T& pod) { pod = jv.isUndefined() ? T() : fromJson(jv); } template inline void fromJson(const QJsonDocument& jd, T& pod) { pod = fromJson(jd); } template inline void fillFromJson(const QJsonValue& jv, T& pod) { if (jv.isObject()) JsonObjectConverter::fillFrom(jv.toObject(), pod); else if (!jv.isUndefined()) pod = fromJson(jv); } // JsonConverter<> specialisations template struct TrivialJsonDumper { // Works for: QJsonValue (and all things it can consume), // QJsonObject, QJsonArray static auto dump(const T& val) { return val; } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toBool(); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toInt(); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toDouble(); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return float(jv.toDouble()); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return qint64(jv.toDouble()); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toString(); } }; template <> struct JsonConverter { static auto dump(const QDateTime& val) { return val.isValid() ? val.toMSecsSinceEpoch() : QJsonValue(); } static auto load(const QJsonValue& jv) { return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); } }; template <> struct JsonConverter { static auto dump(const QDate& val) { return toJson( #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) QDateTime(val) #else val.startOfDay() #endif ); } static auto load(const QJsonValue& jv) { return fromJson(jv).date(); } }; template <> struct JsonConverter { static auto load(const QJsonValue& jv) { // QT_NO_URL_CAST_FROM_STRING makes this a bit more verbose QUrl url; url.setUrl(jv.toString()); return url; } static auto dump(const QUrl& url) { return url.toString(QUrl::FullyEncoded); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toArray(); } }; template <> struct JsonConverter { static QJsonValue dump(const QVariant& v); static QVariant load(const QJsonValue& jv); }; template struct JsonConverter> { static QJsonValue dump(const Omittable& from) { return from.has_value() ? toJson(from.value()) : QJsonValue(); } static Omittable load(const QJsonValue& jv) { if (jv.isUndefined()) return none; return fromJson(jv); } }; template struct JsonArrayConverter { static void dumpTo(QJsonArray& ar, const VectorT& vals) { for (const auto& v : vals) ar.push_back(toJson(v)); } static auto dump(const VectorT& vals) { QJsonArray ja; dumpTo(ja, vals); return ja; } static auto load(const QJsonArray& ja) { VectorT vect; vect.reserve(typename VectorT::size_type(ja.size())); for (const auto& i : ja) vect.push_back(fromJson(i)); return vect; } static auto load(const QJsonValue& jv) { return load(jv.toArray()); } static auto load(const QJsonDocument& jd) { return load(jd.array()); } }; template struct JsonConverter> : public JsonArrayConverter> {}; #if QT_VERSION_MAJOR < 6 // QVector is an alias of QList in Qt6 but not in Qt 5 template struct JsonConverter> : public JsonArrayConverter> {}; #endif template struct JsonConverter> : public JsonArrayConverter> {}; template <> struct JsonConverter : public JsonArrayConverter { static auto dump(const QStringList& sl) { return QJsonArray::fromStringList(sl); } }; template <> struct JsonObjectConverter> { static void dumpTo(QJsonObject& json, const QSet& s) { for (const auto& e : s) json.insert(toJson(e), QJsonObject {}); } static void fillFrom(const QJsonObject& json, QSet& s) { s.reserve(s.size() + json.size()); for (auto it = json.begin(); it != json.end(); ++it) s.insert(it.key()); } }; template struct HashMapFromJson { static void dumpTo(QJsonObject& json, const HashMapT& hashMap) { for (auto it = hashMap.begin(); it != hashMap.end(); ++it) json.insert(it.key(), toJson(it.value())); } static void fillFrom(const QJsonObject& jo, HashMapT& h) { h.reserve(h.size() + jo.size()); for (auto it = jo.begin(); it != jo.end(); ++it) h[it.key()] = fromJson(it.value()); } }; template struct JsonObjectConverter> : public HashMapFromJson> {}; template struct JsonObjectConverter> : public HashMapFromJson> {}; template <> struct JsonConverter { static QJsonObject dump(const QVariantHash& vh); static QVariantHash load(const QJsonValue& jv); }; // Conditional insertion into a QJsonObject namespace _impl { template inline void addTo(QJsonObject& o, const QString& k, ValT&& v) { o.insert(k, toJson(v)); } template inline void addTo(QUrlQuery& q, const QString& k, ValT&& v) { q.addQueryItem(k, QStringLiteral("%1").arg(v)); } // OpenAPI is entirely JSON-based, which means representing bools as // textual true/false, rather than 1/0. inline void addTo(QUrlQuery& q, const QString& k, bool v) { q.addQueryItem(k, v ? QStringLiteral("true") : QStringLiteral("false")); } inline void addTo(QUrlQuery& q, const QString& k, const QUrl& v) { q.addQueryItem(k, v.toEncoded()); } inline void addTo(QUrlQuery& q, const QString& k, const QStringList& vals) { for (const auto& v : vals) q.addQueryItem(k, v); } // This one is for types that don't have isEmpty() and for all types // when Force is true template struct AddNode { template static void impl(ContT& container, const QString& key, ForwardedT&& value) { addTo(container, key, std::forward(value)); } }; // This one is for types that have isEmpty() when Force is false template struct AddNode().isEmpty())> { template static void impl(ContT& container, const QString& key, ForwardedT&& value) { if (!value.isEmpty()) addTo(container, key, std::forward(value)); } }; // This one unfolds Omittable<> (also only when Force is false) template struct AddNode, false> { template static void impl(ContT& container, const QString& key, const OmittableT& value) { if (value) addTo(container, key, *value); } }; } // namespace _impl static constexpr bool IfNotEmpty = false; /*! Add a key-value pair to QJsonObject or QUrlQuery * * Adds a key-value pair(s) specified by \p key and \p value to * \p container, optionally (in case IfNotEmpty is passed for the first * template parameter) taking into account the value "emptiness". * With IfNotEmpty, \p value is NOT added to the container if and only if: * - it has a method `isEmpty()` and `value.isEmpty() == true`, or * - it's an `Omittable<>` and `value.omitted() == true`. * * If \p container is a QUrlQuery, an attempt to fit \p value into it is * made as follows: * - if \p value is a QJsonObject, \p key is ignored and pairs from \p value * are copied to \p container, assuming that the value in each pair * is a string; * - if \p value is a QStringList, it is "exploded" into a list of key-value * pairs with key equal to \p key and value taken from each list item; * - if \p value is a bool, its OpenAPI (i.e. JSON) representation is added * to the query (`true` or `false`, respectively). * * \tparam Force add the pair even if the value is empty. This is true * by default; passing IfNotEmpty or false for this parameter * enables emptiness checks as described above */ template inline void addParam(ContT& container, const QString& key, ValT&& value) { _impl::AddNode, Force>::impl(container, key, std::forward(value)); } } // namespace Quotient