aboutsummaryrefslogtreecommitdiff
path: root/lib/converters.h
diff options
context:
space:
mode:
authorn-peugnet <n.peugnet@free.fr>2022-10-06 19:27:24 +0200
committern-peugnet <n.peugnet@free.fr>2022-10-06 19:27:24 +0200
commitd911b207f49e936b3e992200796110f0749ed150 (patch)
tree96d20ebb4d074a4c1755e21cb316a52d584daee3 /lib/converters.h
parent8ad8a74152c5701b6ca1f9a00487ba9257a439b4 (diff)
parent56c2f2e2b809b9077393eb617828f33d144f5634 (diff)
downloadlibquotient-d911b207f49e936b3e992200796110f0749ed150.tar.gz
libquotient-d911b207f49e936b3e992200796110f0749ed150.zip
New upstream version 0.7.0
Diffstat (limited to 'lib/converters.h')
-rw-r--r--lib/converters.h824
1 files changed, 466 insertions, 358 deletions
diff --git a/lib/converters.h b/lib/converters.h
index 53855a1f..0fb36320 100644
--- a/lib/converters.h
+++ b/lib/converters.h
@@ -1,436 +1,544 @@
-/******************************************************************************
-* Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
-*
-* This library is free software; you can redistribute it and/or
-* modify it under the terms of the GNU Lesser General Public
-* License as published by the Free Software Foundation; either
-* version 2.1 of the License, or (at your option) any later version.
-*
-* This library is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-* Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public
-* License along with this library; if not, write to the Free Software
-* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-*/
+// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
+#include "omittable.h"
#include "util.h"
-#include <QtCore/QJsonObject>
+#include <QtCore/QDate>
#include <QtCore/QJsonArray> // Includes <QtCore/QJsonValue>
#include <QtCore/QJsonDocument>
-#include <QtCore/QDate>
-#include <QtCore/QUrlQuery>
+#include <QtCore/QJsonObject>
#include <QtCore/QSet>
+#include <QtCore/QUrlQuery>
#include <QtCore/QVector>
-#include <unordered_map>
+#include <type_traits>
#include <vector>
-#if 0 // Waiting for C++17
-#include <experimental/optional>
+#include <variant>
+class QVariant;
+
+namespace Quotient {
template <typename T>
-using optional = std::experimental::optional<T>;
-#endif
+struct JsonObjectConverter {
+ // To be implemented in specialisations
+ static void dumpTo(QJsonObject&, const T&) = delete;
+ static void fillFrom(const QJsonObject&, T&) = delete;
+};
-// Enable std::unordered_map<QString, T>
-namespace std
-{
- template <> struct hash<QString>
- {
- size_t operator()(const QString& s) const Q_DECL_NOEXCEPT
- {
- return qHash(s
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- , uint(qGlobalQHashSeed())
-#endif
- );
+template <typename PodT, typename JsonT>
+PodT fromJson(const JsonT&);
+
+template <typename T>
+struct JsonObjectUnpacker {
+ // By default, revert to fromJson() so that one could provide a single
+ // fromJson<T, QJsonObject> specialisation instead of specialising
+ // the entire JsonConverter; if a different type of JSON value is needed
+ // (e.g., an array), specialising JsonConverter is inevitable
+ static T load(const QJsonValue& jv) { return fromJson<T>(jv.toObject()); }
+ static T load(const QJsonDocument& jd) { return fromJson<T>(jd.object()); }
+};
+
+//! \brief The switchboard for extra conversion algorithms behind from/toJson
+//!
+//! This template is mainly intended for partial conversion specialisations
+//! since from/toJson are functions and cannot be partially specialised.
+//! Another case for JsonConverter is to insulate types that can be constructed
+//! from basic types - namely, QVariant and QUrl can be directly constructed
+//! from QString and having an overload or specialisation for those leads to
+//! ambiguity between these and QJsonValue. For trivial (converting
+//! QJsonObject/QJsonValue) and most simple cases such as primitive types or
+//! QString this class is not needed.
+//!
+//! Do NOT call the functions of this class directly unless you know what you're
+//! doing; and do not try to specialise basic things unless you're really sure
+//! that they are not supported and it's not feasible to support those by means
+//! of overloading toJson() and specialising fromJson().
+template <typename T>
+struct JsonConverter : JsonObjectUnpacker<T> {
+ // Unfortunately, if constexpr doesn't work with dump() and T::toJson
+ // because trying to check invocability of T::toJson hits a hard
+ // (non-SFINAE) compilation error if the member is not there. Hence a bit
+ // more verbose SFINAE construct in _impl::JsonExporter.
+ static auto dump(const T& data)
+ {
+ if constexpr (requires() { data.toJson(); })
+ return data.toJson();
+ else {
+ QJsonObject jo;
+ JsonObjectConverter<T>::dumpTo(jo, data);
+ return jo;
}
- };
+ }
+
+ using JsonObjectUnpacker<T>::load;
+ static T load(const QJsonObject& jo)
+ {
+ // 'else' below are required to suppress code generation for unused
+ // branches - 'return' is not enough
+ if constexpr (std::is_same_v<T, QJsonObject>)
+ return jo;
+ else if constexpr (std::is_constructible_v<T, QJsonObject>)
+ return T(jo);
+ else {
+ T pod;
+ JsonObjectConverter<T>::fillFrom(jo, pod);
+ return pod;
+ }
+ }
+};
+
+template <typename T>
+inline auto toJson(const T& pod)
+// -> can return anything from which QJsonValue or, in some cases, QJsonDocument
+// is constructible
+{
+ if constexpr (std::is_constructible_v<QJsonValue, T>)
+ return pod; // No-op if QJsonValue can be directly constructed
+ else
+ return JsonConverter<T>::dump(pod);
}
-class QVariant;
+template <typename T>
+inline void fillJson(QJsonObject& json, const T& data)
+{
+ JsonObjectConverter<T>::dumpTo(json, data);
+}
-namespace QMatrixClient
+template <typename PodT, typename JsonT>
+inline PodT fromJson(const JsonT& json)
{
- // This catches anything implicitly convertible to QJsonValue/Object/Array
- inline auto toJson(const QJsonValue& val) { return val; }
- inline auto toJson(const QJsonObject& o) { return o; }
- inline auto toJson(const QJsonArray& arr) { return arr; }
- // Special-case QString to avoid ambiguity between QJsonValue
- // and QVariant (also, QString.isEmpty() is used in _impl::AddNode<> below)
- inline auto toJson(const QString& s) { return s; }
-
- inline QJsonArray toJson(const QStringList& strings)
- {
- return QJsonArray::fromStringList(strings);
- }
+ // JsonT here can be whatever the respective JsonConverter specialisation
+ // accepts but by default it's QJsonValue, QJsonDocument, or QJsonObject
+ return JsonConverter<PodT>::load(json);
+}
+
+// Convenience fromJson() overload that deduces PodT instead of requiring
+// the coder to explicitly type it. It still enforces the
+// overwrite-everything semantics of fromJson(), unlike fillFromJson()
+
+template <typename JsonT, typename PodT>
+inline void fromJson(const JsonT& json, PodT& pod)
+{
+ pod = fromJson<PodT>(json);
+}
+
+template <typename T>
+inline void fillFromJson(const QJsonValue& jv, T& pod)
+{
+ if constexpr (requires() { JsonObjectConverter<T>::fillFrom({}, pod); }) {
+ JsonObjectConverter<T>::fillFrom(jv.toObject(), pod);
+ return;
+ } else if (!jv.isUndefined())
+ pod = fromJson<T>(jv);
+}
+
+namespace _impl {
+ void warnUnknownEnumValue(const QString& stringValue,
+ const char* enumTypeName);
+ void reportEnumOutOfBounds(uint32_t v, const char* enumTypeName);
+}
+
+//! \brief Facility string-to-enum converter
+//!
+//! This is to simplify enum loading from JSON - just specialise
+//! Quotient::fromJson() and call this function from it, passing (aside from
+//! the JSON value for the enum - that must be a string, not an int) any
+//! iterable container of string'y values (const char*, QLatin1String, etc.)
+//! matching respective enum values, 0-based.
+//! \sa enumToJsonString
+template <typename EnumT, typename EnumStringValuesT>
+EnumT enumFromJsonString(const QString& s, const EnumStringValuesT& enumValues,
+ EnumT defaultValue)
+{
+ static_assert(std::is_unsigned_v<std::underlying_type_t<EnumT>>);
+ if (const auto it = std::find(cbegin(enumValues), cend(enumValues), s);
+ it != cend(enumValues))
+ return EnumT(it - cbegin(enumValues));
+
+ if (!s.isEmpty())
+ _impl::warnUnknownEnumValue(s, qt_getEnumName(EnumT()));
+ return defaultValue;
+}
+
+//! \brief Facility enum-to-string converter
+//!
+//! This does the same as enumFromJsonString, the other way around.
+//! \note The source enumeration must not have gaps in values, or \p enumValues
+//! has to match those gaps (i.e., if the source enumeration is defined
+//! as <tt>{ Value1 = 1, Value2 = 3, Value3 = 5 }</tt> then \p enumValues
+//! should be defined as <tt>{ "", "Value1", "", "Value2", "", "Value3"
+//! }</tt> (mind the gap at value 0, in particular).
+//! \sa enumFromJsonString
+template <typename EnumT, typename EnumStringValuesT>
+QString enumToJsonString(EnumT v, const EnumStringValuesT& enumValues)
+{
+ static_assert(std::is_unsigned_v<std::underlying_type_t<EnumT>>);
+ if (v < size(enumValues))
+ return enumValues[v];
+
+ _impl::reportEnumOutOfBounds(static_cast<uint32_t>(v),
+ qt_getEnumName(EnumT()));
+ Q_ASSERT(false);
+ return {};
+}
- inline QString toJson(const QByteArray& bytes)
+//! \brief Facility converter for flags
+//!
+//! This is very similar to enumFromJsonString, except that the target
+//! enumeration is assumed to be of a 'flag' kind - i.e. its values must be
+//! a power-of-two sequence starting from 1, without gaps, so exactly 1,2,4,8,16
+//! and so on.
+//! \note Unlike enumFromJsonString, the values start from 1 and not from 0,
+//! with 0 being used for an invalid value by default.
+//! \note This function does not support flag combinations.
+//! \sa QUO_DECLARE_FLAGS, QUO_DECLARE_FLAGS_NS
+template <typename FlagT, typename FlagStringValuesT>
+FlagT flagFromJsonString(const QString& s, const FlagStringValuesT& flagValues,
+ FlagT defaultValue = FlagT(0U))
+{
+ // Enums based on signed integers don't make much sense for flag types
+ static_assert(std::is_unsigned_v<std::underlying_type_t<FlagT>>);
+ if (const auto it = std::find(cbegin(flagValues), cend(flagValues), s);
+ it != cend(flagValues))
+ return FlagT(1U << (it - cbegin(flagValues)));
+
+ if (!s.isEmpty())
+ _impl::warnUnknownEnumValue(s, qt_getEnumName(FlagT()));
+ return defaultValue;
+}
+
+template <typename FlagT, typename FlagStringValuesT>
+QString flagToJsonString(FlagT v, const FlagStringValuesT& flagValues)
+{
+ static_assert(std::is_unsigned_v<std::underlying_type_t<FlagT>>);
+ if (const auto offset =
+ qCountTrailingZeroBits(std::underlying_type_t<FlagT>(v));
+ offset < size(flagValues)) //
{
- return bytes.constData();
+ return flagValues[offset];
}
- // QVariant is outrageously omnivorous - it consumes whatever is not
- // exactly matching the signature of other toJson overloads. The trick
- // below disables implicit conversion to QVariant through its numerous
- // non-explicit constructors.
- QJsonValue variantToJson(const QVariant& v);
- template <typename T>
- inline auto toJson(T&& /* const QVariant& or QVariant&& */ var)
- -> std::enable_if_t<std::is_same<std::decay_t<T>, QVariant>::value,
- QJsonValue>
+ _impl::reportEnumOutOfBounds(static_cast<uint32_t>(v),
+ qt_getEnumName(FlagT()));
+ Q_ASSERT(false);
+ return {};
+}
+
+// Specialisations
+
+template<>
+inline bool fromJson(const QJsonValue& jv) { return jv.toBool(); }
+
+template <>
+inline int fromJson(const QJsonValue& jv) { return jv.toInt(); }
+
+template <>
+inline double fromJson(const QJsonValue& jv) { return jv.toDouble(); }
+
+template <>
+inline float fromJson(const QJsonValue& jv) { return float(jv.toDouble()); }
+
+template <>
+inline qint64 fromJson(const QJsonValue& jv) { return qint64(jv.toDouble()); }
+
+template <>
+inline QString fromJson(const QJsonValue& jv) { return jv.toString(); }
+
+//! Use fromJson<QString> and then toLatin1()/toUtf8()/... to make QByteArray
+//!
+//! QJsonValue can only convert to QString and there's ambiguity whether
+//! conversion to QByteArray should use (fast but very limited) toLatin1() or
+//! (all encompassing and conforming to the JSON spec but slow) toUtf8().
+template <>
+inline QByteArray fromJson(const QJsonValue& jv) = delete;
+
+template <>
+inline QJsonArray fromJson(const QJsonValue& jv) { return jv.toArray(); }
+
+template <>
+inline QJsonArray fromJson(const QJsonDocument& jd) { return jd.array(); }
+
+inline QJsonValue toJson(const QDateTime& val)
+{
+ return val.isValid() ? val.toMSecsSinceEpoch() : QJsonValue();
+}
+template <>
+inline QDateTime fromJson(const QJsonValue& jv)
+{
+ return QDateTime::fromMSecsSinceEpoch(fromJson<qint64>(jv), Qt::UTC);
+}
+
+inline QJsonValue toJson(const QDate& val) { return toJson(val.startOfDay()); }
+template <>
+inline QDate fromJson(const QJsonValue& jv)
+{
+ return fromJson<QDateTime>(jv).date();
+}
+
+// Insulate QVariant and QUrl conversions into JsonConverter so that they don't
+// interfere with toJson(const QJsonValue&) over QString, since both types are
+// constructible from QString (even if QUrl requires explicit construction).
+
+template <>
+struct JsonConverter<QUrl> {
+ static auto load(const QJsonValue& jv)
{
- return variantToJson(var);
+ return QUrl(jv.toString());
}
- QJsonObject toJson(const QMap<QString, QVariant>& map);
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
- QJsonObject toJson(const QHash<QString, QVariant>& hMap);
-#endif
-
- template <typename T>
- inline QJsonArray toJson(const std::vector<T>& vals)
+ static auto dump(const QUrl& url)
{
- QJsonArray ar;
- for (const auto& v: vals)
- ar.push_back(toJson(v));
- return ar;
+ return url.toString(QUrl::FullyEncoded);
}
+};
+
+template <>
+struct QUOTIENT_API JsonConverter<QVariant> {
+ static QJsonValue dump(const QVariant& v);
+ static QVariant load(const QJsonValue& jv);
+};
+
+template <typename... Ts>
+inline QJsonValue toJson(const std::variant<Ts...>& v)
+{
+ // std::visit requires all overloads to return the same type - and
+ // QJsonValue is a perfect candidate for that same type (assuming that
+ // variants never occur on the top level in Matrix API)
+ return std::visit(
+ [](const auto& value) { return QJsonValue { toJson(value) }; }, v);
+}
- template <typename T>
- inline QJsonArray toJson(const QVector<T>& vals)
+template <typename T>
+struct JsonConverter<std::variant<QString, T>> {
+ static std::variant<QString, T> load(const QJsonValue& jv)
{
- QJsonArray ar;
- for (const auto& v: vals)
- ar.push_back(toJson(v));
- return ar;
+ if (jv.isString())
+ return fromJson<QString>(jv);
+ return fromJson<T>(jv);
}
+};
- template <typename T>
- inline QJsonObject toJson(const QSet<T>& set)
+template <typename T>
+struct JsonConverter<Omittable<T>> {
+ static QJsonValue dump(const Omittable<T>& from)
{
- QJsonObject json;
- for (auto e: set)
- json.insert(toJson(e), QJsonObject{});
- return json;
+ return from.has_value() ? toJson(from.value()) : QJsonValue();
}
-
- template <typename T>
- inline QJsonObject toJson(const QHash<QString, T>& hashMap)
+ static Omittable<T> load(const QJsonValue& jv)
{
- QJsonObject json;
- for (auto it = hashMap.begin(); it != hashMap.end(); ++it)
- json.insert(it.key(), toJson(it.value()));
- return json;
+ if (jv.isUndefined())
+ return none;
+ return fromJson<T>(jv);
}
+};
- template <typename T>
- inline QJsonObject toJson(const std::unordered_map<QString, T>& hashMap)
+template <typename VectorT, typename T = typename VectorT::value_type>
+struct JsonArrayConverter {
+ static auto dump(const VectorT& vals)
{
- QJsonObject json;
- for (auto it = hashMap.begin(); it != hashMap.end(); ++it)
- json.insert(it.key(), toJson(it.value()));
- return json;
+ QJsonArray ja;
+ for (const auto& v : vals)
+ ja.push_back(toJson(v));
+ return ja;
+ }
+ static auto load(const QJsonArray& ja)
+ {
+ VectorT vect;
+ vect.reserve(typename VectorT::size_type(ja.size()));
+ // NB: Make sure to pass QJsonValue to fromJson<> so that it could
+ // hit the correct overload and not fall back to the generic fromJson
+ // that treats everything as an object. See also the explanation in
+ // the commit introducing these lines.
+ for (const QJsonValue v : ja)
+ vect.push_back(fromJson<T>(v));
+ return vect;
}
+ static auto load(const QJsonValue& jv) { return load(jv.toArray()); }
+ static auto load(const QJsonDocument& jd) { return load(jd.array()); }
+};
- template <typename T>
- struct FromJsonObject
- {
- T operator()(const QJsonObject& jo) const { return T(jo); }
- };
+template <typename T>
+struct JsonConverter<std::vector<T>>
+ : public JsonArrayConverter<std::vector<T>> {};
- template <typename T>
- struct FromJson
- {
- T operator()(const QJsonValue& jv) const
- {
- return FromJsonObject<T>()(jv.toObject());
- }
- T operator()(const QJsonDocument& jd) const
- {
- return FromJsonObject<T>()(jd.object());
- }
- };
+#if QT_VERSION_MAJOR < 6 // QVector is an alias of QList in Qt6 but not in Qt 5
+template <typename T>
+struct JsonConverter<QVector<T>> : public JsonArrayConverter<QVector<T>> {};
+#endif
+
+template <typename T>
+struct JsonConverter<QList<T>> : public JsonArrayConverter<QList<T>> {};
- template <typename T>
- inline auto fromJson(const QJsonValue& jv)
+template <>
+struct JsonConverter<QStringList> : public JsonArrayConverter<QStringList> {
+ static auto dump(const QStringList& sl)
{
- return FromJson<T>()(jv);
+ return QJsonArray::fromStringList(sl);
}
+};
- template <typename T>
- inline auto fromJson(const QJsonDocument& jd)
+template <>
+struct JsonObjectConverter<QSet<QString>> {
+ static void dumpTo(QJsonObject& json, const QSet<QString>& s)
{
- return FromJson<T>()(jd);
+ for (const auto& e : s)
+ json.insert(e, QJsonObject {});
}
-
- template <> struct FromJson<bool>
+ static void fillFrom(const QJsonObject& json, QSet<QString>& s)
{
- auto operator()(const QJsonValue& jv) const { return jv.toBool(); }
- };
+ s.reserve(s.size() + json.size());
+ for (auto it = json.begin(); it != json.end(); ++it)
+ s.insert(it.key());
+ }
+};
- template <> struct FromJson<int>
+template <typename HashMapT>
+struct HashMapFromJson {
+ static void dumpTo(QJsonObject& json, const HashMapT& hashMap)
{
- auto operator()(const QJsonValue& jv) const { return jv.toInt(); }
- };
+ 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());
+ // NB: the QJsonValue cast below is for the same reason as in
+ // JsonArrayConverter
+ for (auto it = jo.begin(); it != jo.end(); ++it)
+ h[it.key()] = fromJson<typename HashMapT::mapped_type>(
+ QJsonValue(it.value()));
+ }
+};
- template <> struct FromJson<double>
- {
- auto operator()(const QJsonValue& jv) const { return jv.toDouble(); }
- };
+template <typename T, typename HashT>
+struct JsonObjectConverter<std::unordered_map<QString, T, HashT>>
+ : public HashMapFromJson<std::unordered_map<QString, T, HashT>> {};
- template <> struct FromJson<float>
- {
- auto operator()(const QJsonValue& jv) const { return float(jv.toDouble()); }
- };
+template <typename T>
+struct JsonObjectConverter<QHash<QString, T>>
+ : public HashMapFromJson<QHash<QString, T>> {};
- template <> struct FromJson<qint64>
- {
- auto operator()(const QJsonValue& jv) const { return qint64(jv.toDouble()); }
- };
+QJsonObject QUOTIENT_API toJson(const QVariantHash& vh);
+template <>
+QVariantHash QUOTIENT_API fromJson(const QJsonValue& jv);
- template <> struct FromJson<QString>
- {
- auto operator()(const QJsonValue& jv) const { return jv.toString(); }
- };
+// Conditional insertion into a QJsonObject
- template <> struct FromJson<QDateTime>
- {
- auto operator()(const QJsonValue& jv) const
- {
- return QDateTime::fromMSecsSinceEpoch(fromJson<qint64>(jv), Qt::UTC);
- }
- };
+constexpr bool IfNotEmpty = false;
- template <> struct FromJson<QDate>
+namespace _impl {
+ template <typename ValT>
+ inline void addTo(QJsonObject& o, const QString& k, ValT&& v)
{
- auto operator()(const QJsonValue& jv) const
- {
- return fromJson<QDateTime>(jv).date();
- }
- };
+ o.insert(k, toJson(v));
+ }
- template <> struct FromJson<QJsonArray>
+ template <typename ValT>
+ inline void addTo(QUrlQuery& q, const QString& k, ValT&& v)
{
- auto operator()(const QJsonValue& jv) const
- {
- return jv.toArray();
- }
- };
+ q.addQueryItem(k, QStringLiteral("%1").arg(v));
+ }
- template <> struct FromJson<QByteArray>
+ // 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)
{
- auto operator()(const QJsonValue& jv) const
- {
- return fromJson<QString>(jv).toLatin1();
- }
- };
+ q.addQueryItem(k, v ? QStringLiteral("true") : QStringLiteral("false"));
+ }
- template <> struct FromJson<QVariant>
+ inline void addTo(QUrlQuery& q, const QString& k, const QUrl& v)
{
- QVariant operator()(const QJsonValue& jv) const;
- };
+ q.addQueryItem(k, v.toEncoded());
+ }
- template <typename VectorT>
- struct ArrayFromJson
+ inline void addTo(QUrlQuery& q, const QString& k, const QStringList& vals)
{
- auto operator()(const QJsonArray& ja) const
- {
- using size_type = typename VectorT::size_type;
- VectorT vect; vect.resize(size_type(ja.size()));
- std::transform(ja.begin(), ja.end(),
- vect.begin(), FromJson<typename VectorT::value_type>());
- return vect;
- }
- auto operator()(const QJsonValue& jv) const
- {
- return operator()(jv.toArray());
- }
- auto operator()(const QJsonDocument& jd) const
- {
- return operator()(jd.array());
- }
- };
-
- template <typename T>
- struct FromJson<std::vector<T>> : ArrayFromJson<std::vector<T>>
- { };
-
- template <typename T>
- struct FromJson<QVector<T>> : ArrayFromJson<QVector<T>>
- { };
+ for (const auto& v : vals)
+ q.addQueryItem(k, v);
+ }
- template <typename T> struct FromJson<QList<T>>
- {
- auto operator()(const QJsonValue& jv) const
+ // This one is for types that don't have isEmpty() and for all types
+ // when Force is true
+ template <typename ValT, bool Force = true, typename = bool>
+ struct AddNode {
+ template <typename ContT, typename ForwardedT>
+ static void impl(ContT& container, const QString& key,
+ ForwardedT&& value)
{
- const auto jsonArray = jv.toArray();
- QList<T> sl; sl.reserve(jsonArray.size());
- std::transform(jsonArray.begin(), jsonArray.end(),
- std::back_inserter(sl), FromJson<T>());
- return sl;
+ addTo(container, key, std::forward<ForwardedT>(value));
}
};
- template <> struct FromJson<QStringList> : FromJson<QList<QString>> { };
-
- template <> struct FromJson<QMap<QString, QVariant>>
- {
- QMap<QString, QVariant> operator()(const QJsonValue& jv) const;
- };
-
- template <typename T> struct FromJson<QSet<T>>
- {
- auto operator()(const QJsonValue& jv) const
+ // This one is for types that have isEmpty() when Force is false
+ template <typename ValT>
+ struct AddNode<ValT, IfNotEmpty, decltype(std::declval<ValT>().isEmpty())> {
+ template <typename ContT, typename ForwardedT>
+ static void impl(ContT& container, const QString& key,
+ ForwardedT&& value)
{
- const auto json = jv.toObject();
- QSet<T> s; s.reserve(json.size());
- for (auto it = json.begin(); it != json.end(); ++it)
- s.insert(it.key());
- return s;
+ if (!value.isEmpty())
+ addTo(container, key, std::forward<ForwardedT>(value));
}
};
- template <typename HashMapT>
- struct HashMapFromJson
- {
- auto operator()(const QJsonObject& jo) const
- {
- HashMapT h; h.reserve(jo.size());
- for (auto it = jo.begin(); it != jo.end(); ++it)
- h[it.key()] =
- fromJson<typename HashMapT::mapped_type>(it.value());
- return h;
- }
- auto operator()(const QJsonValue& jv) const
- {
- return operator()(jv.toObject());
- }
- auto operator()(const QJsonDocument& jd) const
+ // This one unfolds Omittable<> (also only when IfNotEmpty is requested)
+ template <typename ValT>
+ struct AddNode<Omittable<ValT>, IfNotEmpty> {
+ template <typename ContT, typename OmittableT>
+ static void impl(ContT& container, const QString& key,
+ const OmittableT& value)
{
- return operator()(jd.object());
+ if (value)
+ addTo(container, key, *value);
}
};
+} // namespace _impl
+
+/*! 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 <bool Force = true, typename ContT, typename ValT>
+inline void addParam(ContT& container, const QString& key, ValT&& value)
+{
+ _impl::AddNode<std::decay_t<ValT>, Force>::impl(container, key,
+ std::forward<ValT>(value));
+}
- template <typename T>
- struct FromJson<std::unordered_map<QString, T>>
- : HashMapFromJson<std::unordered_map<QString, T>>
- { };
-
- template <typename T>
- struct FromJson<QHash<QString, T>> : HashMapFromJson<QHash<QString, T>>
- { };
-
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
- template <> struct FromJson<QHash<QString, QVariant>>
- {
- QHash<QString, QVariant> operator()(const QJsonValue& jv) const;
- };
-#endif
-
- // Conditional insertion into a QJsonObject
-
- namespace _impl
- {
- template <typename ValT>
- inline void addTo(QJsonObject& o, const QString& k, ValT&& v)
- { o.insert(k, toJson(v)); }
-
- template <typename ValT>
- 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 QStringList& vals)
- {
- for (const auto& v: vals)
- q.addQueryItem(k, v);
- }
-
- inline void addTo(QUrlQuery& q, const QString&, const QJsonObject& vals)
- {
- for (auto it = vals.begin(); it != vals.end(); ++it)
- q.addQueryItem(it.key(), it.value().toString());
+// This is a facility function to convert camelCase method/variable names
+// used throughout Quotient to snake_case JSON keys - see usage in
+// single_key_value.h and event.h (DEFINE_CONTENT_GETTER macro).
+inline auto toSnakeCase(QLatin1String s)
+{
+ QString result { s };
+ for (auto it = result.begin(); it != result.end(); ++it)
+ if (it->isUpper()) {
+ const auto offset = static_cast<int>(it - result.begin());
+ result.insert(offset, '_'); // NB: invalidates iterators
+ it = result.begin() + offset + 1;
+ *it = it->toLower();
}
-
- // This one is for types that don't have isEmpty()
- template <typename ValT, bool Force = true, typename = bool>
- struct AddNode
- {
- template <typename ContT, typename ForwardedT>
- static void impl(ContT& container, const QString& key,
- ForwardedT&& value)
- {
- addTo(container, key, std::forward<ForwardedT>(value));
- }
- };
-
- // This one is for types that have isEmpty()
- template <typename ValT>
- struct AddNode<ValT, false,
- decltype(std::declval<ValT>().isEmpty())>
- {
- template <typename ContT, typename ForwardedT>
- static void impl(ContT& container, const QString& key,
- ForwardedT&& value)
- {
- if (!value.isEmpty())
- AddNode<ValT>::impl(container,
- key, std::forward<ForwardedT>(value));
- }
- };
-
- // This is a special one that unfolds Omittable<>
- template <typename ValT, bool Force>
- struct AddNode<Omittable<ValT>, Force>
- {
- template <typename ContT, typename OmittableT>
- static void impl(ContT& container,
- const QString& key, const OmittableT& value)
- {
- if (!value.omitted())
- AddNode<ValT>::impl(container, key, value.value());
- else if (Force) // Edge case, no value but must put something
- AddNode<ValT>::impl(container, key, QString{});
- }
- };
-
-#if 0
- // This is a special one that unfolds optional<>
- template <typename ValT, bool Force>
- struct AddNode<optional<ValT>, Force>
- {
- template <typename ContT, typename OptionalT>
- static void impl(ContT& container,
- const QString& key, const OptionalT& value)
- {
- if (value)
- AddNode<ValT>::impl(container, key, value.value());
- else if (Force) // Edge case, no value but must put something
- AddNode<ValT>::impl(container, key, QString{});
- }
- };
-#endif
-
- } // namespace _impl
-
- static constexpr bool IfNotEmpty = false;
-
- template <bool Force = true, typename ContT, typename ValT>
- inline void addParam(ContT& container, const QString& key, ValT&& value)
- {
- _impl::AddNode<std::decay_t<ValT>, Force>
- ::impl(container, key, std::forward<ValT>(value));
- }
-} // namespace QMatrixClient
+ return result;
+}
+} // namespace Quotient