#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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "./errorhandling.h" namespace ReflectiveRapidJSON { template struct JsonSerializable; /*! * \brief The AdaptedJsonSerializable class allows considering 3rd party classes as serializable. */ template struct AdaptedJsonSerializable : public Traits::Bool { 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::max() ? std::numeric_limits::max() : static_cast(size); } /*! * \brief Serializes the specified JSON \a document. */ inline RAPIDJSON_NAMESPACE::StringBuffer serializeJsonDocToString(RAPIDJSON_NAMESPACE::Document &document) { RAPIDJSON_NAMESPACE::StringBuffer buffer; RAPIDJSON_NAMESPACE::Writer 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 using IsBuiltInType = Traits::Any, std::is_floating_point, std::is_pointer, std::is_enum, Traits::IsSpecializationOf, Traits::IsSpecializationOf, Traits::IsIteratable, Traits::IsSpecializationOf, Traits::IsSpecializationOf, Traits::IsSpecializationOf, IsVariant>; template using IsCustomType = Traits::Not>; // 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 using IsJsonSerializable = Traits::Any>, std::is_base_of, Type>, AdaptedJsonSerializable>; // define functions to "push" values to a RapidJSON array or object /*! * \brief Pushes the specified \a reflectable to the specified value. */ template > * = 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 > * = 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 > * = 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 > * = 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 > * = 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 > * = 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 > *> 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 , std::is_floating_point> * = nullptr> inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) { value.Set(reflectable, allocator); } /*! * \brief Pushes the specified enumeration item to the specified value. */ template > * = nullptr> inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) { value.Set(static_cast::type>, std::uint64_t, std::int64_t>>(reflectable), allocator); } /*! * \brief Pushes the specified C-string to the specified value. */ template , std::is_same> * = 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 > * = 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 > * = 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 , Traits::HasSize> * = 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 , Traits::Not>> * = 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 > * = 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 > * = 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 struct TuplePushHelper { static void push(const Tuple &tuple, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) { TuplePushHelper::push(tuple, value, allocator); JsonReflector::push(std::get(tuple), value, allocator); } }; template struct TuplePushHelper { 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 > * = 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::value, allocator); Detail::TuplePushHelper::value>::push(reflectable, array, allocator); } /*! * \brief Pushes the specified pair to the specified value. */ template > * = 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 or weak_ptr to the specified value. */ template , Traits::IsSpecializationOf, Traits::IsSpecializationOf> * = 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 > * = 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::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 > *> 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 > *> 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 > *> 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 > *> 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 > * = nullptr> void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue>::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 > * = nullptr> void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue> &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 , Traits::Not>> * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &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 , Traits::IsReservable> * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &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 > * = nullptr> void pull(Type &reflectable, rapidjson::GenericValue>::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 > * = nullptr> void pull(Type &reflectable, rapidjson::GenericValue>::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 > * = nullptr> void pull(Type &reflectable, rapidjson::GenericValue>::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 > * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &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 > * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &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 > * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &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 > * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors); /*! * \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null. */ template > * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors); /*! * \brief Pulls the specified \a reflectable which is a shared_ptr from the specified value which might be null. */ template > * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors); /*! * \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null. */ template > * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors); /*! * \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type. */ template inline void pull( Type &reflectable, rapidjson::GenericValue>::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 inline void pull(Type &reflectable, const char *name, const rapidjson::GenericValue>::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 > *> void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue> &value, JsonDeserializationErrors *errors); /*! * \brief Pulls the integer or float from the specified value which is supposed and checked to contain the right type. */ template >, Traits::Any, std::is_floating_point>> * = nullptr> inline void pull( Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsNumber()) { if (errors) { errors->reportTypeMismatch(value.GetType()); } return; } reflectable = value.Is() ? value.Get() : static_cast(value.GetDouble()); } /*! * \brief Pulls the boolean from the specified value which is supposed and checked to contain the right type. */ template > * = nullptr> inline void pull( Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsBool()) { if (errors) { errors->reportTypeMismatch(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 > * = nullptr> inline void pull( Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue> &value, JsonDeserializationErrors *errors) { using ExpectedType = Traits::Conditional::type>, std::uint64_t, std::int64_t>; if (!value.Is()) { if (errors) { errors->reportTypeMismatch(value.GetType()); } return; } reflectable = static_cast(value.Get()); } /*! * \brief Pulls the std::string from the specified value which is supposed and checked to contain a string. */ template > * = nullptr> inline void pull( Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsString()) { if (errors) { errors->reportTypeMismatch(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 , std::is_same, std::is_same> * = nullptr> inline void pull(Type &, const RAPIDJSON_NAMESPACE::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsString()) { if (errors) { errors->reportTypeMismatch(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 , Traits::Not>> *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsArray()) { if (errors) { errors->reportTypeMismatch(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 , Traits::IsReservable> *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsArray()) { if (errors) { errors->reportTypeMismatch(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 > *> void pull(Type &reflectable, rapidjson::GenericValue>::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> &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 > *> void pull(Type &reflectable, rapidjson::GenericValue>::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> &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(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 > *> void pull(Type &reflectable, rapidjson::GenericValue>::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> &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(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 > *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsObject()) { if (errors) { errors->reportTypeMismatch(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 > *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsObject()) { if (errors) { errors->reportTypeMismatch(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 struct TuplePullHelper { static void pull(Tuple &tuple, const RAPIDJSON_NAMESPACE::Value::ConstArray value, JsonDeserializationErrors *errors) { TuplePullHelper::pull(tuple, value, errors); JsonReflector::pull::type>(std::get(tuple), value[N - 1], errors); } }; template struct TuplePullHelper { static void pull(Tuple &tuple, const RAPIDJSON_NAMESPACE::Value::ConstArray value, JsonDeserializationErrors *errors) { JsonReflector::pull::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 > *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsArray()) { if (errors) { errors->reportTypeMismatch(value.GetType()); } return; } const auto array = value.GetArray(); if (array.Size() != std::tuple_size::value) { if (errors) { // FIXME: report expected and actual size errors->reportArraySizeMismatch(); } return; } Detail::TuplePullHelper::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 > *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsArray()) { if (errors) { errors->reportTypeMismatch(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 > *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (value.IsNull()) { reflectable.reset(); return; } reflectable = std::make_unique(); pull(*reflectable, value, errors); } /*! * \brief Pulls the specified \a reflectable which is a shared_ptr from the specified value which might be null. */ template > *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (value.IsNull()) { reflectable.reset(); return; } reflectable = std::make_shared(); pull(*reflectable, value, errors); } /// \cond namespace Detail { template void assignVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if constexpr (compiletimeIndex < std::variant_size_v) { if (compiletimeIndex == runtimeIndex) { if constexpr (std::is_same_v, std::monostate>) { variant = std::monostate{}; } else { pull(variant.template emplace(), value, errors); } } else { assignVariantValueByRuntimeIndex(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 > *> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsObject()) { if (errors) { errors->reportTypeMismatch(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 inline void pull(Type &reflectable, rapidjson::GenericValue>::ValueIterator &value, JsonDeserializationErrors *errors) { pull(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 inline void pull(Type &reflectable, const char *name, const rapidjson::GenericValue>::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; if (errors) { previousMember = errors->currentMember; errors->currentMember = name; } // actually pull value for member pull(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 > *> void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue> &value, JsonDeserializationErrors *errors) { if (!value.IsObject()) { if (errors) { errors->reportTypeMismatch(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 , IsMapOrHash, IsMultiMapOrHash> * = 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 , std::is_floating_point> * = 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 > * = 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 > * = 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 > * = 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 > * = 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 , IsMapOrHash, IsMultiMapOrHash, std::is_integral, std::is_floating_point, Traits::IsString, IsArray> * = 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 , IsMapOrHash, IsMultiMapOrHash> * = 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(doc.GetType()); } return Type(); } Type res; pull(res, doc.GetObject(), errors); return res; } /*! * \brief Deserializes the specified JSON to \tparam Type which is an integer, float or boolean. */ template , std::is_floating_point> * = nullptr> Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr) { RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize)); if (!doc.Is()) { if (errors) { errors->reportTypeMismatch(doc.GetType()); } return Type(); } return doc.Get(); } /*! * \brief Deserializes the specified JSON to \tparam Type which is a std::string. */ template > * = 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(doc.GetType()); } return Type(); } return doc.GetString(); } /*! * \brief Deserializes the specified JSON to \tparam Type which can be mapped to an array. */ template > * = 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(doc.GetType()); } return Type(); } Type res; pull(res, doc.GetArray(), errors); return res; } /*! * \brief Deserializes the specified JSON from an null-terminated C-string to \tparam Type. */ template Type fromJson(const char *json, JsonDeserializationErrors *errors = nullptr) { return fromJson(json, std::strlen(json), errors); } /*! * \brief Deserializes the specified JSON from an std::string to \tparam Type. */ template Type fromJson(const std::string &json, JsonDeserializationErrors *errors = nullptr) { return fromJson(json.data(), json.size(), errors); } } // namespace JsonReflector } // namespace ReflectiveRapidJSON #endif // REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_H