#ifndef REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_H
#define REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_H

/*!
 * \file reflector.h
 * \brief Contains functions to (de)serialize basic types such as int, double, bool, std::string,
 *        std::vector, ... with RapidJSON.
 */

#include "../traits.h"

#include <c++utilities/application/global.h>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>

#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>

#include "./errorhandling.h"

namespace ReflectiveRapidJSON {

template <typename Type> struct JsonSerializable;

/*!
 * \brief The AdaptedJsonSerializable class allows considering 3rd party classes as serializable.
 */
template <typename T> struct AdaptedJsonSerializable : public Traits::Bool<false> {
    static constexpr const char *name = "AdaptedJsonSerializable";
    static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedJsonSerializable";
};

/*!
 * \brief The JsonReflector namespace contains helper functions to ease the use of RapidJSON for automatic (de)serialization.
 */
namespace JsonReflector {

/*!
 * \brief Casts the specified \a size to the size type used by RapidJSON ensuring no overflow happens.
 */
constexpr RAPIDJSON_NAMESPACE::SizeType rapidJsonSize(std::size_t size)
{
    return size > std::numeric_limits<RAPIDJSON_NAMESPACE::SizeType>::max() ? std::numeric_limits<RAPIDJSON_NAMESPACE::SizeType>::max()
                                                                            : static_cast<RAPIDJSON_NAMESPACE::SizeType>(size);
}

/*!
 * \brief Serializes the specified JSON \a document.
 */
inline RAPIDJSON_NAMESPACE::StringBuffer serializeJsonDocToString(RAPIDJSON_NAMESPACE::Document &document)
{
    RAPIDJSON_NAMESPACE::StringBuffer buffer;
    RAPIDJSON_NAMESPACE::Writer<RAPIDJSON_NAMESPACE::StringBuffer> writer(buffer);
    document.Accept(writer);
    return buffer;
}

/*!
 * \brief Parses the specified JSON string.
 */
inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, std::size_t jsonSize)
{
    RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kObjectType);
    const RAPIDJSON_NAMESPACE::ParseResult parseRes = document.Parse(json, jsonSize);
    if (parseRes.IsError()) {
        throw parseRes;
    }
    return document;
}

// define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes
template <typename Type>
using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>,
    Traits::IsSpecializingAnyOf<Type, std::tuple, std::pair>, Traits::IsIteratable<Type>,
    Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;

// define trait to check for custom structs/classes which are JSON serializable
// NOTE: the check for Traits::IsComplete is required because std::is_base_of fails for incomplete types when using GCC
template <typename Type>
using IsJsonSerializable
    = Traits::Any<Traits::Not<Traits::IsComplete<Type>>, std::is_base_of<JsonSerializable<Type>, Type>, AdaptedJsonSerializable<Type>>;

// define functions to "push" values to a RapidJSON array or object

/*!
 * \brief Pushes the specified \a reflectable to the specified value.
 */
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);

/*!
 * \brief Pushes the \a reflectable to the specified array.
 */
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);

/*!
 * \brief Pushes the \a reflectable which has a custom type to the specified array.
 */
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);

/*!
 * \brief Pushes the specified \a reflectable which has custom type as a member to the specified object.
 */
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>> * = nullptr>
void push(
    const Type &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);

/*!
 * \brief Pushes the specified \a reflectable as a member to the specified object.
 */
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>> * = nullptr>
void push(
    const Type &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);

/*!
 * \brief Pushes the \a reflectable which has a custom type to the specified object.
 * \remarks The definition of this function must be provided by the code generator or Boost.Hana.
 */
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);

/*!
 * \brief Pushes the specified \a reflectable which has a custom type to the specified value.
 */
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> *>
inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.SetObject();
    RAPIDJSON_NAMESPACE::Value::Object obj(value.GetObject());
    push(reflectable, obj, allocator);
}

/*!
 * \brief Pushes the specified integer/float/boolean to the specified value.
 */
template <typename Type,
    Traits::EnableIfAny<
        Traits::All<std::is_integral<Type>, Traits::Not<std::is_same<Type, std::uint8_t>>, Traits::Not<std::is_same<Type, std::int8_t>>>,
        std::is_floating_point<Type>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.Set(reflectable, allocator);
}

/*!
 * \brief Pushes the specified 8-bit integer to the specified value.
 */
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::uint8_t>, std::is_same<Type, std::int8_t>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.Set(static_cast<int>(reflectable), allocator);
}

/*!
 * \brief Pushes the specified enumeration item to the specified value.
 */
template <typename Type, Traits::EnableIfAny<std::is_enum<Type>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.Set(static_cast<Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, std::uint64_t, std::int64_t>>(reflectable),
        allocator);
}

/*!
 * \brief Pushes the specified C-string to the specified value.
 */
template <typename Type, Traits::EnableIfAny<std::is_same<Type, const char *>, std::is_same<Type, const char *const &>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    if (reflectable) {
        value.SetString(reflectable, allocator);
    } else {
        value.SetNull();
    }
}

/*!
 * \brief Pushes the specified std::string_view to the specified value.
 */
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string_view>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    if (reflectable.data()) {
        value.SetString(reflectable.data(), rapidJsonSize(reflectable.size()), allocator);
    } else {
        value.SetNull();
    }
}

/*!
 * \brief Pushes the specified std::string to the specified value.
 */
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.SetString(reflectable.data(), rapidJsonSize(reflectable.size()), allocator);
}

/*!
 * \brief Pushes the specified iteratable (eg. std::vector, std::list) to the specified value.
 */
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::HasSize<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.SetArray();
    RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray());
    array.Reserve(rapidJsonSize(reflectable.size()), allocator);
    for (const auto &item : reflectable) {
        push(item, array, allocator);
    }
}

/*!
 * \brief Pushes the specified iteratable list (eg. std::vector, std::list) to the specified value.
 */
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::Not<Traits::HasSize<Type>>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.SetArray();
    RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray());
    for (const auto &item : reflectable) {
        push(item, array, allocator);
    }
}

/*!
 * \brief Pushes the specified map (std::map, std::unordered_map) to the specified value.
 */
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.SetObject();
    RAPIDJSON_NAMESPACE::Value::Object object(value.GetObject());
    for (const auto &item : reflectable) {
        push(item.second, item.first.data(), object, allocator);
    }
}

/*!
 * \brief Pushes the specified multimap (std::multimap, std::unordered_multimap) to the specified value.
 */
template <typename Type, Traits::EnableIfAny<IsMultiMapOrHash<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.SetObject();
    for (const auto &item : reflectable) {
        const auto memberName = RAPIDJSON_NAMESPACE::Value::StringRefType(item.first.data(), rapidJsonSize(item.first.size()));
        const auto existingMember = value.FindMember(memberName);
        const auto arrayAlreadyExists
            = existingMember != value.MemberEnd() && existingMember->value.GetType() == RAPIDJSON_NAMESPACE::Type::kArrayType;
        auto newArrayValue = RAPIDJSON_NAMESPACE::Value{ RAPIDJSON_NAMESPACE::kArrayType };
        RAPIDJSON_NAMESPACE::Value::Array array = arrayAlreadyExists ? existingMember->value.GetArray() : newArrayValue.GetArray();
        push(item.second, array, allocator);
        if (!arrayAlreadyExists) {
            value.AddMember(memberName, newArrayValue, allocator);
        }
    }
}

namespace Detail {

/*!
 * \brief The TuplePushHelper class helps serializing tuples to JSON arrays.
 */
template <class Tuple, std::size_t N> struct TuplePushHelper {
    static void push(const Tuple &tuple, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
    {
        TuplePushHelper<Tuple, N - 1>::push(tuple, value, allocator);
        JsonReflector::push(std::get<N - 1>(tuple), value, allocator);
    }
};

template <class Tuple> struct TuplePushHelper<Tuple, 1> {
    static void push(const Tuple &tuple, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
    {
        JsonReflector::push(std::get<0>(tuple), value, allocator);
    }
};
} // namespace Detail

/*!
 * \brief Pushes the specified tuple to the specified value.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.SetArray();
    RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray());
    array.Reserve(std::tuple_size<Type>::value, allocator);
    Detail::TuplePushHelper<Type, std::tuple_size<Type>::value>::push(reflectable, array, allocator);
}

/*!
 * \brief Pushes the specified pair to the specified value.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    value.SetArray();
    RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray());
    array.Reserve(2, allocator);
    push(reflectable.first, array, allocator);
    push(reflectable.second, array, allocator);
}

/*!
 * \brief Pushes the specified unique_ptr, shared_ptr, weak_ptr or optional to the specified value.
 */
template <typename Type,
    Traits::EnableIfAny<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    if (!reflectable) {
        value.SetNull();
        return;
    }
    push(*reflectable, value, allocator);
}

/*!
 * \brief Pushes the specified variant to the specified value.
 */
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    if (reflectable.valueless_by_exception()) {
        value.SetNull();
        return;
    }

    RAPIDJSON_NAMESPACE::Value index, data;
    index.SetUint64(reflectable.index());
    std::visit(
        [&data, &allocator](const auto &reflectableOfActualType) {
            if constexpr (!std::is_same_v<std::decay_t<decltype(reflectableOfActualType)>, std::monostate>) {
                push(reflectableOfActualType, data, allocator);
            } else {
                CPP_UTILITIES_UNUSED(data)
                CPP_UTILITIES_UNUSED(allocator)
            }
        },
        reflectable);

    value.SetObject();
    value.AddMember("index", index, allocator);
    value.AddMember("data", data, allocator);
}

/*!
 * \brief Pushes the specified \a reflectable which has a custom type to the specified array.
 */
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>> *>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    RAPIDJSON_NAMESPACE::Value objectValue(RAPIDJSON_NAMESPACE::kObjectType);
    RAPIDJSON_NAMESPACE::Value::Object object(objectValue.GetObject());
    push(reflectable, object, allocator);
    value.PushBack(objectValue, allocator);
}

/*!
 * \brief Pushes the specified \a reflectable to the specified array.
 */
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>> *>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    RAPIDJSON_NAMESPACE::Value genericValue;
    push(reflectable, genericValue, allocator);
    value.PushBack(genericValue, allocator);
}

/*!
 * \brief Pushes the specified \a reflectable which has custom type as a member to the specified object.
 */
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>> *>
void push(
    const Type &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    RAPIDJSON_NAMESPACE::Value objectValue(RAPIDJSON_NAMESPACE::kObjectType);
    RAPIDJSON_NAMESPACE::Value::Object object(objectValue.GetObject());
    push(reflectable, object, allocator);
    value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), objectValue, allocator);
}

/*!
 * \brief Pushes the specified \a reflectable as a member to the specified object.
 */
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>> *>
void push(
    const Type &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
    RAPIDJSON_NAMESPACE::Value genericValue;
    push(reflectable, genericValue, allocator);
    value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), genericValue, allocator);
}

// define functions to "pull" values from a RapidJSON array or object

/*!
 * \brief Pulls the \a reflectable which has a custom type from the specified object.
 * \remarks The definition of this function must be provided by the code generator or Boost.Hana.
 */
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> * = nullptr>
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value,
    JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the \a reflectable which has a custom type from the specified value which is supposed and checked to contain an object.
 */
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> * = nullptr>
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is an iteratable without reserve() method from the specified value which is checked to contain an array.
 */
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::Not<Traits::IsReservable<Type>>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is an iteratable with reserve() method from the specified value which is checked to contain an array.
 */
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::IsReservable<Type>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is an array/vector/list from the specified array. The \a reflectable is cleared before.
 */
template <typename Type, Traits::EnableIf<IsArray<Type>> * = nullptr>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a set from the specified array. The \a reflectable is cleared before.
 */
template <typename Type, Traits::EnableIf<IsSet<Type>> * = nullptr>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a multiset from the specified array. The \a reflectable is cleared before.
 */
template <typename Type, Traits::EnableIf<IsMultiSet<Type>> * = nullptr>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a map from the specified value which is checked to contain an object.
 */
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a multimap from the specified value which is checked to contain an object.
 */
template <typename Type, Traits::EnableIf<IsMultiMapOrHash<Type>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a tuple from the specified value which is checked to contain an array.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a pair from the specified value which is checked to contain an array.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a shared_ptr from the specified value which might be null.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is an std::optional from the specified value which might be null.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null.
 */
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type.
 */
template <typename Type>
inline void pull(
    Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the specified member of \a reflectable which has a custom type from the specified object.
 * \remarks It is checked whether the object actually contains the member. If not, the missing member is ignored. So currently all members
 *          are optional.
 */
template <typename Type>
inline void pull(Type &reflectable, const char *name, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value,
    JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the \a reflectable which has a custom type from the specified value which is supposed and checked to contain an object.
 */
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> *>
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);

/*!
 * \brief Pulls the integer or float from the specified value which is supposed and checked to contain the right type.
 */
template <typename Type,
    Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Not<std::is_same<Type, std::uint8_t>>,
        Traits::Not<std::is_same<Type, std::int8_t>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>> * = nullptr>
inline void pull(
    Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsNumber()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    reflectable = value.Is<Type>() ? value.Get<Type>() : static_cast<Type>(value.GetDouble());
}

/*!
 * \brief Pulls the integer or float from the specified value which is supposed and checked to contain the right type.
 */
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::uint8_t>, std::is_same<Type, std::int8_t>> * = nullptr>
inline void pull(
    Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    int i = 0;
    pull(i, value, errors);
    if (value.IsNumber()) {
        reflectable = static_cast<Type>(i);
    }
}

/*!
 * \brief Pulls the boolean from the specified value which is supposed and checked to contain the right type.
 */
template <typename Type, Traits::EnableIf<std::is_same<Type, bool>> * = nullptr>
inline void pull(
    Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsBool()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    reflectable = value.GetBool();
}

/*!
 * \brief Pulls the specified enumeration item from the specified value which is supposed and checked to be compatible with the underlying type.
 * \remarks It is *not* checked, whether \a value is actually a valid enum item.
 */
template <typename Type, Traits::EnableIfAny<std::is_enum<Type>> * = nullptr>
inline void pull(
    Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    using ExpectedType = Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, std::uint64_t, std::int64_t>;
    if (!value.Is<ExpectedType>()) {
        if (errors) {
            errors->reportTypeMismatch<ExpectedType>(value.GetType());
        }
        return;
    }
    reflectable = static_cast<Type>(value.Get<ExpectedType>());
}

/*!
 * \brief Pulls the std::string from the specified value which is supposed and checked to contain a string.
 */
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
inline void pull(
    Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsString()) {
        if (errors) {
            errors->reportTypeMismatch<std::string>(value.GetType());
        }
        return;
    }
    reflectable = value.GetString();
}

/*!
 * \brief Checks whether the specified value contains a string.
 * \remarks Does not actually store the value since the ownership would not be clear (see README.md).
 */
template <typename Type,
    Traits::EnableIfAny<std::is_same<Type, const char *>, std::is_same<Type, const char *const &>, std::is_same<Type, std::string_view>> * = nullptr>
inline void pull(Type &, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsString()) {
        if (errors) {
            errors->reportTypeMismatch<std::string>(value.GetType());
        }
        return;
    }
}

/*!
 * \brief Pulls the specified \a reflectable which is an iteratable without reserve() method from the specified value which is checked to contain an array.
 */
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::Not<Traits::IsReservable<Type>>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsArray()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    pull(reflectable, value.GetArray(), errors);
}

/*!
 * \brief Pulls the specified \a reflectable which is an iteratable with reserve() method from the specified value which is checked to contain an array.
 */
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::IsReservable<Type>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsArray()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    auto array = value.GetArray();
    reflectable.reserve(array.Size());
    pull(reflectable, array, errors);
}

/*!
 * \brief Pulls the specified \a reflectable which is an array/vector/list from the specified array. The \a reflectable is cleared before.
 */
template <typename Type, Traits::EnableIf<IsArray<Type>> *>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors)
{
    // clear previous contents of the array
    reflectable.clear();

    // pull all array elements of the specified value
    std::size_t index = 0;
    for (const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &item : array) {
        // set error context for current index
        if (errors) {
            errors->currentIndex = index;
        }
        ++index;
        reflectable.emplace_back();
        pull(reflectable.back(), item, errors);
    }

    // clear error context
    if (errors) {
        errors->currentIndex = JsonDeserializationError::noIndex;
    }
}

/*!
 * \brief Pulls the specified \a reflectable which is a multiset from the specified array. The \a reflectable is cleared before.
 */
template <typename Type, Traits::EnableIf<IsMultiSet<Type>> *>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors)
{
    // clear previous contents of the array
    reflectable.clear();

    // pull all array elements of the specified value
    std::size_t index = 0;
    for (const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &item : array) {
        // set error context for current index
        if (errors) {
            errors->currentIndex = index;
        }
        ++index;
        typename Type::value_type itemObj;
        pull(itemObj, item, errors);
        reflectable.emplace(std::move(itemObj));
    }

    // clear error context
    if (errors) {
        errors->currentIndex = JsonDeserializationError::noIndex;
    }
}

/*!
 * \brief Pulls the specified \a reflectable which is a set from the specified array. The \a reflectable is cleared before.
 */
template <typename Type, Traits::EnableIf<IsSet<Type>> *>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors)
{
    // clear previous contents of the array
    reflectable.clear();

    // pull all array elements of the specified value
    std::size_t index = 0;
    for (const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &item : array) {
        // set error context for current index
        if (errors) {
            errors->currentIndex = index;
        }
        ++index;
        typename Type::value_type itemObj;
        pull(itemObj, item, errors);
        if (!reflectable.emplace(std::move(itemObj)).second) {
            errors->reportUnexpectedDuplicate(JsonType::Array);
        }
    }

    // clear error context
    if (errors) {
        errors->currentIndex = JsonDeserializationError::noIndex;
    }
}

/*!
 * \brief Pulls the specified \a reflectable which is a map from the specified value which is checked to contain an object.
 */
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsObject()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    auto obj = value.GetObject();
    for (auto i = obj.MemberBegin(), end = obj.MemberEnd(); i != end; ++i) {
        pull(reflectable[i->name.GetString()], i->value, errors);
    }
}

/*!
 * \brief Pulls the specified \a reflectable which is a multimap from the specified value which is checked to contain an object.
 */
template <typename Type, Traits::EnableIf<IsMultiMapOrHash<Type>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsObject()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    auto obj = value.GetObject();
    for (auto i = obj.MemberBegin(), end = obj.MemberEnd(); i != end; ++i) {
        if (i->value.GetType() != RAPIDJSON_NAMESPACE::kArrayType) {
            auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type()));
            pull(insertedIterator->second, i->value, errors);
            continue;
        }
        const auto array = i->value.GetArray();
        for (const auto &arrayValue : array) {
            auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type()));
            pull(insertedIterator->second, arrayValue, errors);
        }
    }
}

namespace Detail {

/*!
 * \brief The TuplePullHelper class helps deserializing tuples from JSON arrays.
 * \remarks Assumes that the array bounds have been checked before (to match the size of the tuple).
 */
template <class Tuple, std::size_t N> struct TuplePullHelper {
    static void pull(Tuple &tuple, const RAPIDJSON_NAMESPACE::Value::ConstArray value, JsonDeserializationErrors *errors)
    {
        TuplePullHelper<Tuple, N - 1>::pull(tuple, value, errors);
        JsonReflector::pull<typename std::tuple_element<N - 1, Tuple>::type>(std::get<N - 1>(tuple), value[N - 1], errors);
    }
};

template <class Tuple> struct TuplePullHelper<Tuple, 1> {
    static void pull(Tuple &tuple, const RAPIDJSON_NAMESPACE::Value::ConstArray value, JsonDeserializationErrors *errors)
    {
        JsonReflector::pull<typename std::tuple_element<0, Tuple>::type>(std::get<0>(tuple), value[0], errors);
    }
};
} // namespace Detail

/*!
 * \brief Pulls the specified \a reflectable which is a tuple from the specified value which is checked to contain an array.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsArray()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    const auto array = value.GetArray();
    if (array.Size() != std::tuple_size<Type>::value) {
        if (errors) {
            // FIXME: report expected and actual size
            errors->reportArraySizeMismatch();
        }
        return;
    }
    Detail::TuplePullHelper<Type, std::tuple_size<Type>::value>::pull(reflectable, array, errors);
}

/*!
 * \brief Pulls the specified \a reflectable which is a pair from the specified value which is checked to contain an array.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsArray()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    const auto array = value.GetArray();
    if (array.Size() != 2) {
        if (errors) {
            // FIXME: report expected and actual size
            errors->reportArraySizeMismatch();
        }
        return;
    }
    pull(reflectable.first, array[0], errors);
    pull(reflectable.second, array[1], errors);
}

/*!
 * \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (value.IsNull()) {
        reflectable.reset();
        return;
    }
    reflectable = std::make_unique<typename Type::element_type>();
    pull(*reflectable, value, errors);
}

/*!
 * \brief Pulls the specified \a reflectable which is a shared_ptr from the specified value which might be null.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (value.IsNull()) {
        reflectable.reset();
        return;
    }
    reflectable = std::make_shared<typename Type::element_type>();
    pull(*reflectable, value, errors);
}

/*!
 * \brief Pulls the specified \a reflectable which is an std::optional from the specified value which might be null.
 */
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (value.IsNull()) {
        reflectable.reset();
    } else {
        pull(reflectable.emplace(), value, errors);
    }
}

/// \cond
namespace Detail {
template <typename Variant, std::size_t compiletimeIndex = 0>
void assignVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant,
    const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if constexpr (compiletimeIndex < std::variant_size_v<Variant>) {
        if (compiletimeIndex == runtimeIndex) {
            if constexpr (std::is_same_v<std::variant_alternative_t<compiletimeIndex, Variant>, std::monostate>) {
                variant = std::monostate{};
            } else {
                pull(variant.template emplace<compiletimeIndex>(), value, errors);
            }
        } else {
            assignVariantValueByRuntimeIndex<Variant, compiletimeIndex + 1>(runtimeIndex, variant, value, errors);
        }
    } else {
        if (errors) {
            errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, JsonType::Number, errors->currentRecord,
                errors->currentMember, errors->currentIndex);
        }
    }
}
} // namespace Detail
/// \endcond

/*!
 * \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null.
 */
template <typename Type, Traits::EnableIf<IsVariant<Type>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsObject()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }

    auto obj = value.GetObject();
    auto indexIterator = obj.FindMember("index");
    auto dataIterator = obj.FindMember("data");
    if (indexIterator == obj.MemberEnd() || dataIterator == obj.MemberEnd()) {
        if (errors) {
            errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantObject, JsonType::Object, JsonType::Object, errors->currentRecord,
                errors->currentMember, errors->currentIndex);
        }
        return;
    }
    const auto &indexValue = indexIterator->value;
    if (!indexValue.IsUint64()) {
        if (errors) {
            errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, jsonType(indexValue.GetType()),
                errors->currentRecord, errors->currentMember, errors->currentIndex);
        }
        return;
    }
    Detail::assignVariantValueByRuntimeIndex(indexValue.GetUint64(), reflectable, dataIterator->value, errors);
}

/*!
 * \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type.
 */
template <typename Type>
inline void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JsonDeserializationErrors *errors)
{
    pull<Type>(reflectable, *value, errors);
    ++value;
}

/*!
 * \brief Pulls the specified member of \a reflectable which has a custom type from the specified object.
 * \remarks It is checked whether the object actually contains the member. If not, the missing member is ignored. So currently all members
 *          are optional.
 */
template <typename Type>
inline void pull(Type &reflectable, const char *name, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value,
    JsonDeserializationErrors *errors)
{
    // find member
    const auto member = value.FindMember(name);
    if (member == value.MemberEnd()) {
        return; // TODO: handle member missing
    }

    // set error context for current member
    const char *previousMember = nullptr;
    if (errors) {
        previousMember = errors->currentMember;
        errors->currentMember = name;
    }

    // actually pull value for member
    pull<Type>(reflectable, member->value, errors);

    // restore previous error context
    if (errors) {
        errors->currentMember = previousMember;
    }
}

/*!
 * \brief Pulls the \a reflectable which has a custom type from the specified value which is supposed and checked to contain an object.
 */
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> *>
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
    if (!value.IsObject()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(value.GetType());
        }
        return;
    }
    pull(reflectable, value.GetObject(), errors);
}

// define functions providing high-level JSON serialization

/*!
 * \brief Serializes the specified \a reflectable which has a custom type or can be mapped to and object.
 */
template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr>
RAPIDJSON_NAMESPACE::Document toJsonDocument(const Type &reflectable)
{
    RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kObjectType);
    push(reflectable, document, document.GetAllocator());
    return document;
}

/*!
 * \brief Serializes the specified \a reflectable which is an integer, float or boolean.
 */
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr>
RAPIDJSON_NAMESPACE::Document toJsonDocument(Type reflectable)
{
    RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kNumberType);
    document.Set(reflectable, document.GetAllocator());
    return document;
}

/*!
 * \brief Serializes the specified \a reflectable which is an std::string.
 */
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
RAPIDJSON_NAMESPACE::Document toJsonDocument(const std::string &reflectable)
{
    RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
    document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), document.GetAllocator());
    return document;
}

/*!
 * \brief Serializes the specified \a reflectable which is a C-string.
 */
template <typename Type, Traits::EnableIf<std::is_same<Type, const char *>> * = nullptr>
RAPIDJSON_NAMESPACE::Document toJsonDocument(const char *reflectable)
{
    RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
    document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), document.GetAllocator());
    return document;
}

/*!
 * \brief Serializes the specified \a reflectable which is an std::string_view.
 */
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string_view>> * = nullptr>
RAPIDJSON_NAMESPACE::Document toJsonDocument(std::string_view reflectable)
{
    RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
    document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), document.GetAllocator());
    return document;
}

/*!
 * \brief Serializes the specified \a reflectable which can be mapped to an array.
 */
template <typename Type, Traits::EnableIf<IsArray<Type>> * = nullptr> RAPIDJSON_NAMESPACE::Document toJsonDocument(const Type &reflectable)
{
    RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kArrayType);
    push(reflectable, document, document.GetAllocator());
    return document;
}

/*!
 * \brief Serializes the specified \a reflectable.
 */
template <typename Type,
    Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>, std::is_integral<Type>, std::is_floating_point<Type>,
        Traits::IsString<Type>, IsArray<Type>> * = nullptr>
RAPIDJSON_NAMESPACE::StringBuffer toJson(const Type &reflectable)
{
    auto document(toJsonDocument(reflectable));
    return serializeJsonDocToString(document);
}

// define functions providing high-level JSON deserialization

/*!
 * \brief Deserializes the specified JSON to \tparam Type which is a custom type or can be mapped to an object.
 */
template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr>
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
{
    RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
    if (!doc.IsObject()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(doc.GetType());
        }
        return Type();
    }

    Type res;
    pull<Type>(res, doc.GetObject(), errors);
    return res;
}

/*!
 * \brief Deserializes the specified JSON to \tparam Type which is an integer, float or boolean.
 */
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr>
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
{
    RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
    if (!doc.Is<Type>()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(doc.GetType());
        }
        return Type();
    }

    return doc.Get<Type>();
}

/*!
 * \brief Deserializes the specified JSON to \tparam Type which is a std::string.
 */
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
{
    RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
    if (!doc.IsString()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(doc.GetType());
        }
        return Type();
    }

    return doc.GetString();
}

/*!
 * \brief Deserializes the specified JSON to \tparam Type which can be mapped to an array.
 */
template <typename Type, Traits::EnableIf<IsArray<Type>> * = nullptr>
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
{
    RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
    if (!doc.IsArray()) {
        if (errors) {
            errors->reportTypeMismatch<Type>(doc.GetType());
        }
        return Type();
    }

    Type res;
    pull<Type>(res, doc.GetArray(), errors);
    return res;
}

/*!
 * \brief Deserializes the specified JSON from an null-terminated C-string to \tparam Type.
 */
template <typename Type> Type fromJson(const char *json, JsonDeserializationErrors *errors = nullptr)
{
    return fromJson<Type>(json, std::strlen(json), errors);
}

/*!
 * \brief Deserializes the specified JSON from an std::string to \tparam Type.
 */
template <typename Type> Type fromJson(const std::string &json, JsonDeserializationErrors *errors = nullptr)
{
    return fromJson<Type>(json.data(), json.size(), errors);
}

} // namespace JsonReflector
} // namespace ReflectiveRapidJSON

#endif // REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_H
