Allow deserializing tuples

This commit is contained in:
Martchus 2017-10-29 22:53:02 +01:00
parent d849144504
commit 407de04d91
3 changed files with 112 additions and 7 deletions

View File

@ -23,6 +23,7 @@ namespace ReflectiveRapidJSON {
*/
enum class JsonDeserializationErrorKind : byte {
TypeMismatch,
ArraySizeMismatch,
};
/*!
@ -144,6 +145,7 @@ struct JsonDeserializationErrors : public std::vector<JsonDeserializationError>
JsonDeserializationErrors();
template <typename ExpectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType);
void reportArraySizeMismatch();
/// \brief The name of the class or struct which is currently being processed.
const char *currentRecord;
@ -152,7 +154,7 @@ struct JsonDeserializationErrors : public std::vector<JsonDeserializationError>
/// \brief The index in the array which is currently processed.
std::size_t currentIndex;
/// \brief The list of fatal error types in form of flags.
enum class ThrowOn : unsigned char { None = 0, TypeMismatch = 0x1 } throwOn;
enum class ThrowOn : unsigned char { None = 0, TypeMismatch = 0x1, ArraySizeMismatch = 0x2 } throwOn;
};
/*!
@ -175,7 +177,7 @@ constexpr JsonDeserializationErrors::ThrowOn operator|(JsonDeserializationErrors
}
/*!
* \brief Reports a type missmatch between \tparam ExpectedType and \a presentType within the current context.
* \brief Reports a type mismatch between \tparam ExpectedType and \a presentType within the current context.
*/
template <typename ExpectedType> inline void JsonDeserializationErrors::reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType)
{
@ -186,6 +188,19 @@ template <typename ExpectedType> inline void JsonDeserializationErrors::reportTy
}
}
/*!
* \brief Reports an array size mismatch.
* \todo Allow specifying expected and actual size.
* \remarks This can happen when mapping a JSON array to a C++ tuple.
*/
inline void JsonDeserializationErrors::reportArraySizeMismatch()
{
emplace_back(JsonDeserializationErrorKind::ArraySizeMismatch, JsonType::Array, JsonType::Array, currentRecord, currentMember, currentIndex);
if (static_cast<unsigned char>(throwOn) & static_cast<unsigned char>(ThrowOn::ArraySizeMismatch)) {
throw back();
}
}
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_H

View File

@ -135,15 +135,19 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAP
}
namespace Detail {
template <class Tuple, std::size_t N> struct TupleHelper {
/*!
* \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)
{
TupleHelper<Tuple, N - 1>::push(tuple, value, allocator);
TuplePushHelper<Tuple, N - 1>::push(tuple, value, allocator);
Reflector::push(std::get<N - 1>(tuple), value, allocator);
}
};
template <class Tuple> struct TupleHelper<Tuple, 1> {
template <class Tuple> struct TuplePushHelper<Tuple, 1> {
static void push(const Tuple &tuple, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
Reflector::push(std::get<0>(tuple), value, allocator);
@ -160,7 +164,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAP
RAPIDJSON_NAMESPACE::Value arrayValue(RAPIDJSON_NAMESPACE::kArrayType);
RAPIDJSON_NAMESPACE::Value::Array array(arrayValue.GetArray());
array.Reserve(std::tuple_size<Type>::value, allocator);
Detail::TupleHelper<Type, std::tuple_size<Type>::value>::push(reflectable, array, allocator);
Detail::TuplePushHelper<Type, std::tuple_size<Type>::value>::push(reflectable, array, allocator);
value.PushBack(array, allocator);
}
@ -273,7 +277,7 @@ void push(
RAPIDJSON_NAMESPACE::Value arrayValue(RAPIDJSON_NAMESPACE::kArrayType);
RAPIDJSON_NAMESPACE::Value::Array array(arrayValue.GetArray());
array.Reserve(std::tuple_size<Type>::value, allocator);
Detail::TupleHelper<Type, std::tuple_size<Type>::value>::push(reflectable, array, allocator);
Detail::TuplePushHelper<Type, std::tuple_size<Type>::value>::push(reflectable, array, allocator);
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), array, allocator);
}
@ -503,6 +507,75 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
}
}
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::Array &value, JsonDeserializationErrors *errors)
{
TuplePullHelper<Tuple, N - 1>::pull(tuple, value, errors);
Reflector::pull(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::Array &value, JsonDeserializationErrors *errors)
{
Reflector::pull(std::get<0>(tuple), value[0], errors);
}
};
} // namespace Detail
/*!
* \brief Pulls the speciified \a reflectable which is tuple from the specified value iterator which is checked to contain an array.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>>...>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JsonDeserializationErrors *errors)
{
if (!value->IsArray()) {
if (errors) {
errors->reportTypeMismatch<Type>(value->GetType());
}
return;
}
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);
++value;
}
/*!
* \brief Pulls the speciified \a reflectable which is tuple from the specified value iterator which is checked to contain an array.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>>...>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (!value.IsArray()) {
if (errors) {
errors->reportTypeMismatch<Type>(value.GetType());
}
return;
}
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 speciified 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

View File

@ -286,6 +286,7 @@ void JsonReflectorTests::testDeserializePrimitives()
Reflector::pull(double1, array, &errors);
Reflector::pull(bool2, array, &errors);
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
CPPUNIT_ASSERT_EQUAL("a"s, str1);
CPPUNIT_ASSERT_EQUAL(5, int1);
CPPUNIT_ASSERT_EQUAL(5e6f, float1);
@ -293,6 +294,22 @@ void JsonReflectorTests::testDeserializePrimitives()
CPPUNIT_ASSERT_EQUAL(true, bool1);
CPPUNIT_ASSERT_EQUAL(4.125, double1);
CPPUNIT_ASSERT_EQUAL(false, bool2);
// deserialize primitives as tuple
tuple<string, int, float, string, bool, double, bool> arrayAsTuple;
Reflector::pull(arrayAsTuple, doc, &errors);
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
CPPUNIT_ASSERT_EQUAL("a"s, get<0>(arrayAsTuple));
CPPUNIT_ASSERT_EQUAL(5, get<1>(arrayAsTuple));
CPPUNIT_ASSERT_EQUAL(5e6f, get<2>(arrayAsTuple));
CPPUNIT_ASSERT_EQUAL("test"s, get<3>(arrayAsTuple));
CPPUNIT_ASSERT_EQUAL(true, get<4>(arrayAsTuple));
CPPUNIT_ASSERT_EQUAL(4.125, get<5>(arrayAsTuple));
CPPUNIT_ASSERT_EQUAL(false, get<6>(arrayAsTuple));
tuple<string, int> anotherTuple;
Reflector::pull(anotherTuple, doc, &errors);
CPPUNIT_ASSERT_EQUAL(1_st, errors.size());
CPPUNIT_ASSERT_EQUAL(JsonDeserializationErrorKind::ArraySizeMismatch, errors.front().kind);
}
/*!