Keep track of errors and fix usage of Boost.Hana
* Optionally save errors which occur during deserialization to allow error handling * Don't operate on copies when using Boost.Hana
This commit is contained in:
parent
1b1d07ef8c
commit
ad03afb1f2
|
@ -92,10 +92,12 @@ void JSONSerializationCodeGenerator::generate(ostream &os) const
|
|||
|
||||
// print push method
|
||||
os << "template <> inline void push<::" << relevantClass.qualifiedName << ">(const ::" << relevantClass.qualifiedName
|
||||
<< " &reflectable, ::RAPIDJSON_NAMESPACE::Value::Object &value, ::RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)\n{\n";
|
||||
<< " &reflectable, ::RAPIDJSON_NAMESPACE::Value::Object &value, ::RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)\n{\n"
|
||||
" // push base classes\n";
|
||||
for (const RelevantClass *baseClass : relevantBases) {
|
||||
os << " push(static_cast<const ::" << baseClass->qualifiedName << " &>(reflectable), value, allocator);\n";
|
||||
}
|
||||
os << " // push members\n";
|
||||
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
|
||||
os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n";
|
||||
}
|
||||
|
@ -103,13 +105,28 @@ void JSONSerializationCodeGenerator::generate(ostream &os) const
|
|||
|
||||
// print pull method
|
||||
os << "template <> inline void pull<::" << relevantClass.qualifiedName << ">(::" << relevantClass.qualifiedName
|
||||
<< " &reflectable, const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value)\n{\n";
|
||||
<< " &reflectable, const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value, JSONParseErrors "
|
||||
"*errors)\n{\n"
|
||||
" // pull base classes\n";
|
||||
for (const RelevantClass *baseClass : relevantBases) {
|
||||
os << " pull(static_cast<::" << baseClass->qualifiedName << " &>(reflectable), value);\n";
|
||||
os << " pull(static_cast<::" << baseClass->qualifiedName << " &>(reflectable), value, errors);\n";
|
||||
}
|
||||
os << " // set error context for current record\n"
|
||||
" const char *previousRecord;\n"
|
||||
" if (errors) {\n"
|
||||
" previousRecord = errors->currentRecord;\n"
|
||||
" errors->currentRecord = \""
|
||||
<< relevantClass.qualifiedName
|
||||
<< "\";\n"
|
||||
" }\n"
|
||||
" // pull members\n";
|
||||
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
|
||||
os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value);\n";
|
||||
os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, errors);\n";
|
||||
}
|
||||
os << " // restore error context for previous record\n"
|
||||
" if (errors) {\n"
|
||||
" errors->currentRecord = previousRecord;\n"
|
||||
" }\n";
|
||||
os << "}\n\n";
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,27 @@ namespace Reflector {
|
|||
// define code for (de)serializing TestNamespace1::Person objects
|
||||
template <> inline void push<::TestNamespace1::Person>(const ::TestNamespace1::Person &reflectable, ::RAPIDJSON_NAMESPACE::Value::Object &value, ::RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
// push base classes
|
||||
// push members
|
||||
push(reflectable.age, "age", value, allocator);
|
||||
push(reflectable.alive, "alive", value, allocator);
|
||||
}
|
||||
template <> inline void pull<::TestNamespace1::Person>(::TestNamespace1::Person &reflectable, const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value)
|
||||
template <> inline void pull<::TestNamespace1::Person>(::TestNamespace1::Person &reflectable, const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value, JSONParseErrors *errors)
|
||||
{
|
||||
pull(reflectable.age, "age", value);
|
||||
pull(reflectable.alive, "alive", value);
|
||||
// pull base classes
|
||||
// set error context for current record
|
||||
const char *previousRecord;
|
||||
if (errors) {
|
||||
previousRecord = errors->currentRecord;
|
||||
errors->currentRecord = "TestNamespace1::Person";
|
||||
}
|
||||
// pull members
|
||||
pull(reflectable.age, "age", value, errors);
|
||||
pull(reflectable.alive, "alive", value, errors);
|
||||
// restore error context for previous record
|
||||
if (errors) {
|
||||
errors->currentRecord = previousRecord;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Reflector
|
||||
|
|
|
@ -52,7 +52,7 @@ CPPUNIT_TEST_SUITE_REGISTRATION(OverallTests);
|
|||
|
||||
void OverallTests::setUp()
|
||||
{
|
||||
m_expectedCode = toArrayOfLines(readFile(testFilePath("some_structs_json_serialization.h"), 1024));
|
||||
m_expectedCode = toArrayOfLines(readFile(testFilePath("some_structs_json_serialization.h"), 2 * 1024));
|
||||
}
|
||||
|
||||
void OverallTests::tearDown()
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
* \file jsonreflector-boosthana.h
|
||||
* \brief Contains generic functions relying on Boost.Hana which can replace the code which would
|
||||
* otherwise had to be generated.
|
||||
* \remarks
|
||||
* These functions use boost::hana::keys() and boost::hana::at_key() rather than the "plain"
|
||||
* for-loop shown in the introspection examples of the Boost.Hana documentation. The reason is that
|
||||
* the "plain" for-loop involves making copies. This costs performance and - more importantly - prevents
|
||||
* modifying the actual object.
|
||||
*/
|
||||
|
||||
#include "./jsonreflector.h"
|
||||
|
@ -22,8 +27,8 @@ template <typename Type,
|
|||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
boost::hana::for_each(reflectable, [&value, &allocator](auto pair) {
|
||||
push(boost::hana::second(pair), boost::hana::to<char const *>(boost::hana::first(pair)), value, allocator);
|
||||
boost::hana::for_each(boost::hana::keys(reflectable), [&reflectable, &value, &allocator](auto key) {
|
||||
push(boost::hana::at_key(reflectable, key), boost::hana::to<char const *>(key), value, allocator);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -32,19 +37,21 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RA
|
|||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value)
|
||||
void pull(Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JSONParseErrors *errors)
|
||||
{
|
||||
boost::hana::for_each(
|
||||
reflectable, [&value](auto pair) { pull(boost::hana::second(pair), boost::hana::to<char const *>(boost::hana::first(pair)), value); });
|
||||
boost::hana::for_each(boost::hana::keys(reflectable), [&reflectable, &value, &errors](auto key) {
|
||||
pull(boost::hana::at_key(reflectable, key), boost::hana::to<char const *>(key), value, errors);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value)
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value, JSONParseErrors *errors)
|
||||
{
|
||||
boost::hana::for_each(
|
||||
reflectable, [&value](auto pair) { pull(boost::hana::second(pair), boost::hana::to<char const *>(boost::hana::first(pair)), value); });
|
||||
boost::hana::for_each(boost::hana::keys(reflectable), [&reflectable, &value, &errors](auto key) {
|
||||
pull(boost::hana::at_key(reflectable, key), boost::hana::to<char const *>(key), value, errors);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Reflector
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <c++utilities/misc/traits.h>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/pointer.h>
|
||||
#include <rapidjson/rapidjson.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
#include <rapidjson/writer.h>
|
||||
|
@ -21,20 +22,175 @@
|
|||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
enum class ErrorFlags : unsigned char { TypeMismatch, MemberMissing };
|
||||
|
||||
constexpr ErrorFlags operator&(ErrorFlags lhs, ErrorFlags rhs)
|
||||
{
|
||||
return static_cast<ErrorFlags>(static_cast<unsigned char>(lhs) & static_cast<unsigned char>(rhs));
|
||||
}
|
||||
|
||||
constexpr ErrorFlags operator|(ErrorFlags lhs, ErrorFlags rhs)
|
||||
{
|
||||
return static_cast<ErrorFlags>(static_cast<unsigned char>(lhs) | static_cast<unsigned char>(rhs));
|
||||
}
|
||||
|
||||
template <typename Type> struct JSONSerializable;
|
||||
|
||||
/*!
|
||||
* \brief The JSONParseErrorKind enum specifies which kind of error happend when populating variables from parsing results.
|
||||
*/
|
||||
enum class JSONParseErrorKind : byte {
|
||||
TypeMismatch,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The JSONType enum specifies the JSON data type.
|
||||
* \remarks This is currently only used for error handling to propagate expected and actual types in case of a mismatch.
|
||||
*/
|
||||
enum class JSONType : byte {
|
||||
Null,
|
||||
Number,
|
||||
Bool,
|
||||
String,
|
||||
Array,
|
||||
Object,
|
||||
};
|
||||
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>>...>
|
||||
constexpr JSONType jsonType()
|
||||
{
|
||||
return JSONType::Number;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<std::is_same<Type, bool>>...> constexpr JSONType jsonType()
|
||||
{
|
||||
return JSONType::Bool;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<Traits::IsString<Type>, Traits::IsCString<Type>>...> constexpr JSONType jsonType()
|
||||
{
|
||||
return JSONType::String;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsString<Type>>>...> constexpr JSONType jsonType()
|
||||
{
|
||||
return JSONType::Array;
|
||||
}
|
||||
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, Traits::IsString<Type>, Traits::IsCString<Type>,
|
||||
Traits::IsIteratable<Type>>...>
|
||||
constexpr JSONType jsonType()
|
||||
{
|
||||
return JSONType::Object;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Maps the type info provided by RapidJSON to JSONType.
|
||||
*/
|
||||
constexpr JSONType jsonType(RAPIDJSON_NAMESPACE::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case RAPIDJSON_NAMESPACE::kNullType:
|
||||
return JSONType::Null;
|
||||
case RAPIDJSON_NAMESPACE::kFalseType:
|
||||
case RAPIDJSON_NAMESPACE::kTrueType:
|
||||
return JSONType::Bool;
|
||||
case RAPIDJSON_NAMESPACE::kObjectType:
|
||||
return JSONType::Object;
|
||||
case RAPIDJSON_NAMESPACE::kArrayType:
|
||||
return JSONType::Array;
|
||||
case RAPIDJSON_NAMESPACE::kStringType:
|
||||
return JSONType::String;
|
||||
case RAPIDJSON_NAMESPACE::kNumberType:
|
||||
return JSONType::Number;
|
||||
default:
|
||||
return JSONType::Null;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief The JSONParseError struct describes any errors of fromJson() except such caused by invalid JSON.
|
||||
*/
|
||||
struct JSONParseError {
|
||||
JSONParseError(JSONParseErrorKind kind, JSONType expectedType, JSONType actualType, const char *record, const char *member = nullptr,
|
||||
std::size_t index = noIndex);
|
||||
|
||||
/// \brief Which kind of error occured.
|
||||
JSONParseErrorKind kind;
|
||||
/// \brief The expected type (might not be relevant for all error kinds).
|
||||
JSONType expectedType;
|
||||
/// \brief The actual type (might not be relevant for all error kinds).
|
||||
JSONType actualType;
|
||||
/// \brief The name of the class or struct which was being processed when the error was ascertained.
|
||||
const char *record;
|
||||
/// \brief The name of the member which was being processed when the error was ascertained.
|
||||
const char *member;
|
||||
/// \brief The index in the array which was being processed when the error was ascertained.
|
||||
std::size_t index;
|
||||
|
||||
/// \brief Indicates no array was being processed when the error occured.
|
||||
static constexpr std::size_t noIndex = std::numeric_limits<std::size_t>::max();
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new JSONParseError.
|
||||
* \remarks Supposed to be called by JSONParseErrors::reportTypeMismatch() and similar methods of JSONParseErrors.
|
||||
*/
|
||||
inline JSONParseError::JSONParseError(
|
||||
JSONParseErrorKind kind, JSONType expectedType, JSONType actualType, const char *record, const char *member, std::size_t index)
|
||||
: kind(kind)
|
||||
, expectedType(expectedType)
|
||||
, actualType(actualType)
|
||||
, record(record)
|
||||
, member(member)
|
||||
, index(index)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief The JSONParseErrors struct can be passed to fromJson() for error handling.
|
||||
*
|
||||
* When passed to fromJson() and an error occurs, a JSONParseError is added to this object.
|
||||
* If throwOn is set, the JSONParseError is additionally thrown making the error fatal.
|
||||
*
|
||||
* \remarks Errors due to invalid JSON always lead to a RAPIDJSON_NAMESPACE::ParseResult object being thrown. So this
|
||||
* only concerns errors listed in JSONParseErrorKind.
|
||||
*/
|
||||
struct JSONParseErrors : public std::vector<JSONParseError> {
|
||||
JSONParseErrors();
|
||||
|
||||
template <typename ExpectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType);
|
||||
|
||||
/// \brief The name of the class or struct which is currently being processed.
|
||||
const char *currentRecord;
|
||||
/// \brief The name of the member (in currentRecord) which is currently being processed.
|
||||
const char *currentMember;
|
||||
/// \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;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Creates an empty JSONParseErrors object with default context and no errors considered fatal.
|
||||
*/
|
||||
inline JSONParseErrors::JSONParseErrors()
|
||||
: currentRecord("[document]")
|
||||
, currentMember(nullptr)
|
||||
, currentIndex(JSONParseError::noIndex)
|
||||
, throwOn(ThrowOn::None)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Combines to ThrowOn values.
|
||||
*/
|
||||
constexpr JSONParseErrors::ThrowOn operator|(JSONParseErrors::ThrowOn lhs, JSONParseErrors::ThrowOn rhs)
|
||||
{
|
||||
return static_cast<JSONParseErrors::ThrowOn>(static_cast<unsigned char>(lhs) | static_cast<unsigned char>(rhs));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Reports a type missmatch between \tparam ExpectedType and \a presentType within the current context.
|
||||
*/
|
||||
template <typename ExpectedType> inline void JSONParseErrors::reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType)
|
||||
{
|
||||
emplace_back(JSONParseErrorKind::TypeMismatch, jsonType<ExpectedType>(), jsonType(presentType), currentRecord, currentMember, currentIndex);
|
||||
if (static_cast<unsigned char>(throwOn) & static_cast<unsigned char>(ThrowOn::TypeMismatch)) {
|
||||
throw back();
|
||||
}
|
||||
}
|
||||
|
||||
inline RAPIDJSON_NAMESPACE::StringBuffer documentToString(RAPIDJSON_NAMESPACE::Document &document)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::StringBuffer buffer;
|
||||
|
@ -190,132 +346,190 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAP
|
|||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value);
|
||||
void pull(Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JSONParseErrors *errors);
|
||||
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value);
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value, JSONParseErrors *errors);
|
||||
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value)
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value.IsObject()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value.GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
pull<Type>(reflectable, value.GetObject());
|
||||
pull<Type>(reflectable, value.GetObject(), errors);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>>...>
|
||||
inline void pull(Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value)
|
||||
inline void pull(Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value->Is<Type>()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value->GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
reflectable = value->Get<Type>();
|
||||
++value;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>>...>
|
||||
inline void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value)
|
||||
inline void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value.Is<Type>()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value.GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
reflectable = value.Get<Type>();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void pull<std::string>(std::string &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value)
|
||||
inline void pull<std::string>(
|
||||
std::string &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value->IsString()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<std::string>(value->GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
reflectable = value->GetString();
|
||||
++value;
|
||||
}
|
||||
|
||||
template <> inline void pull<std::string>(std::string &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value)
|
||||
template <>
|
||||
inline void pull<std::string>(
|
||||
std::string &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value.IsString()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<std::string>(value.GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
reflectable = value.GetString();
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array);
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JSONParseErrors *errors);
|
||||
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsReservable<Type>>,
|
||||
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value)
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value->IsArray()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value->GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
pull(reflectable, value->GetArray());
|
||||
pull(reflectable, value->GetArray(), errors);
|
||||
++value;
|
||||
}
|
||||
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::IsReservable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value)
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value->IsArray()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value->GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto array = value->GetArray();
|
||||
reflectable.reserve(array.Size());
|
||||
pull(reflectable, array);
|
||||
pull(reflectable, array, errors);
|
||||
++value;
|
||||
}
|
||||
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsReservable<Type>>,
|
||||
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value)
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value.IsArray()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value.GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
pull(reflectable, value.GetArray());
|
||||
pull(reflectable, value.GetArray(), errors);
|
||||
}
|
||||
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::IsReservable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value)
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JSONParseErrors *errors)
|
||||
{
|
||||
if (!value.IsArray()) {
|
||||
return; // TODO: handle type mismatch
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value.GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto array = value.GetArray();
|
||||
reflectable.reserve(array.Size());
|
||||
pull(reflectable, array);
|
||||
pull(reflectable, array, errors);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array)
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JSONParseErrors *errors)
|
||||
{
|
||||
// clear previous contents of the array (TODO: make this configurable)
|
||||
// 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;
|
||||
}
|
||||
reflectable.emplace_back();
|
||||
pull(reflectable.back(), item);
|
||||
pull(reflectable.back(), item, errors);
|
||||
++index;
|
||||
}
|
||||
|
||||
// clear error context
|
||||
if (errors) {
|
||||
errors->currentIndex = JSONParseError::noIndex;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
inline void pull(Type &reflectable, const char *name, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value)
|
||||
inline void pull(
|
||||
Type &reflectable, const char *name, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value, JSONParseErrors *errors)
|
||||
{
|
||||
// find member
|
||||
auto member = value.FindMember(name);
|
||||
if (member == value.MemberEnd()) {
|
||||
return; // TODO: handle member missing
|
||||
}
|
||||
pull<Type>(reflectable, value.FindMember(name)->value);
|
||||
|
||||
// set error context for current member
|
||||
const char *previousMember;
|
||||
if (errors) {
|
||||
previousMember = errors->currentMember;
|
||||
errors->currentMember = name;
|
||||
}
|
||||
|
||||
// actually pull value for member
|
||||
pull<Type>(reflectable, value.FindMember(name)->value, errors);
|
||||
|
||||
// restore previous error context
|
||||
if (errors) {
|
||||
errors->currentMember = previousMember;
|
||||
}
|
||||
}
|
||||
|
||||
// define functions providing high-level JSON serialization
|
||||
|
@ -354,33 +568,44 @@ template <typename Type, Traits::EnableIfAny<std::is_same<Type, const char *>>..
|
|||
|
||||
// define functions providing high-level JSON deserialization
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<std::is_base_of<JSONSerializable<Type>, Type>>...> Type fromJson(const char *json, std::size_t jsonSize)
|
||||
template <typename Type, Traits::EnableIfAny<std::is_base_of<JSONSerializable<Type>, Type>>...>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JSONParseErrors *errors = nullptr)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseDocumentFromString(json, jsonSize));
|
||||
if (!doc.IsObject()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(doc.GetType());
|
||||
}
|
||||
return Type();
|
||||
}
|
||||
|
||||
Type res;
|
||||
pull<Type>(res, doc.GetObject());
|
||||
pull<Type>(res, doc.GetObject(), errors);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>>...>
|
||||
Type fromJson(const char *json, std::size_t jsonSize)
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JSONParseErrors *errors)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseDocumentFromString(json, jsonSize));
|
||||
if (!doc.Is<Type>()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(doc.GetType());
|
||||
}
|
||||
return Type();
|
||||
}
|
||||
|
||||
return doc.Get<Type>();
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::string>>...> Type fromJson(const char *json, std::size_t jsonSize)
|
||||
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::string>>...>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JSONParseErrors *errors)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseDocumentFromString(json, jsonSize));
|
||||
if (!doc.IsString()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(doc.GetType());
|
||||
}
|
||||
return Type();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ template <typename Type> struct JSONSerializable {
|
|||
|
||||
// high-level API
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson() const;
|
||||
static Type fromJson(const char *json, std::size_t jsonSize);
|
||||
static Type fromJson(const char *json);
|
||||
static Type fromJson(const std::string &json);
|
||||
static Type fromJson(const char *json, std::size_t jsonSize, JSONParseErrors *errors = nullptr);
|
||||
static Type fromJson(const char *json, JSONParseErrors *errors = nullptr);
|
||||
static Type fromJson(const std::string &json, JSONParseErrors *errors = nullptr);
|
||||
|
||||
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::JSONSerializable";
|
||||
};
|
||||
|
@ -60,25 +60,25 @@ template <typename Type> RAPIDJSON_NAMESPACE::StringBuffer JSONSerializable<Type
|
|||
/*!
|
||||
* \brief Constructs a new object from the specified JSON.
|
||||
*/
|
||||
template <typename Type> Type JSONSerializable<Type>::fromJson(const char *json, std::size_t jsonSize)
|
||||
template <typename Type> Type JSONSerializable<Type>::fromJson(const char *json, std::size_t jsonSize, JSONParseErrors *errors)
|
||||
{
|
||||
return Reflector::fromJson<Type>(json, jsonSize);
|
||||
return Reflector::fromJson<Type>(json, jsonSize, errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new object from the specified JSON.
|
||||
*/
|
||||
template <typename Type> Type JSONSerializable<Type>::fromJson(const char *json)
|
||||
template <typename Type> Type JSONSerializable<Type>::fromJson(const char *json, JSONParseErrors *errors)
|
||||
{
|
||||
return Reflector::fromJson<Type>(json, std::strlen(json));
|
||||
return Reflector::fromJson<Type>(json, std::strlen(json), errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new object from the specified JSON.
|
||||
*/
|
||||
template <typename Type> Type JSONSerializable<Type>::fromJson(const std::string &json)
|
||||
template <typename Type> Type JSONSerializable<Type>::fromJson(const std::string &json, JSONParseErrors *errors)
|
||||
{
|
||||
return Reflector::fromJson<Type>(json.data(), json.size());
|
||||
return Reflector::fromJson<Type>(json.data(), json.size(), errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -30,16 +30,21 @@ using namespace ReflectiveRapidJSON;
|
|||
/// \cond
|
||||
|
||||
// define some structs for testing serialization
|
||||
struct TestObject : public JSONSerializable<TestObject> {
|
||||
BOOST_HANA_DEFINE_STRUCT(TestObject, (int, number), (double, number2), (vector<int>, numbers), (string, text), (bool, boolean));
|
||||
struct TestObjectHana : public JSONSerializable<TestObjectHana> {
|
||||
//TestObjectHana(){};
|
||||
//TestObjectHana(const TestObjectHana &)
|
||||
//{
|
||||
// std::cout << "copied!!" << std::endl;
|
||||
//};
|
||||
BOOST_HANA_DEFINE_STRUCT(TestObjectHana, (int, number), (double, number2), (vector<int>, numbers), (string, text), (bool, boolean));
|
||||
};
|
||||
|
||||
struct NestingObject : public JSONSerializable<NestingObject> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingObject, (string, name), (TestObject, testObj));
|
||||
struct NestingObjectHana : public JSONSerializable<NestingObjectHana> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingObjectHana, (string, name), (TestObjectHana, testObj));
|
||||
};
|
||||
|
||||
struct NestingArray : public JSONSerializable<NestingArray> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingArray, (string, name), (vector<TestObject>, testObjects));
|
||||
struct NestingArrayHana : public JSONSerializable<NestingArrayHana> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingArrayHana, (string, name), (vector<TestObjectHana>, testObjects));
|
||||
};
|
||||
|
||||
/// \endcond
|
||||
|
@ -56,6 +61,7 @@ class JSONReflectorBoostHanaTests : public TestFixture {
|
|||
CPPUNIT_TEST(testDeserializePrimitives);
|
||||
CPPUNIT_TEST(testDeserializeSimpleObjects);
|
||||
CPPUNIT_TEST(testDeserializeNestedObjects);
|
||||
CPPUNIT_TEST(testHandlingTypeMismatch);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
|
@ -68,6 +74,7 @@ public:
|
|||
void testDeserializePrimitives();
|
||||
void testDeserializeSimpleObjects();
|
||||
void testDeserializeNestedObjects();
|
||||
void testHandlingTypeMismatch();
|
||||
|
||||
private:
|
||||
};
|
||||
|
@ -118,7 +125,7 @@ void JSONReflectorBoostHanaTests::testSerializePrimitives()
|
|||
*/
|
||||
void JSONReflectorBoostHanaTests::testSerializeSimpleObjects()
|
||||
{
|
||||
TestObject testObj;
|
||||
TestObjectHana testObj;
|
||||
testObj.number = 42;
|
||||
testObj.number2 = 3.141592653589793;
|
||||
testObj.numbers = { 1, 2, 3, 4 };
|
||||
|
@ -133,9 +140,9 @@ void JSONReflectorBoostHanaTests::testSerializeSimpleObjects()
|
|||
*/
|
||||
void JSONReflectorBoostHanaTests::testSerializeNestedObjects()
|
||||
{
|
||||
NestingObject nestingObj;
|
||||
NestingObjectHana nestingObj;
|
||||
nestingObj.name = "nesting";
|
||||
TestObject &testObj = nestingObj.testObj;
|
||||
TestObjectHana &testObj = nestingObj.testObj;
|
||||
testObj.number = 42;
|
||||
testObj.number2 = 3.141592653589793;
|
||||
testObj.numbers = { 1, 2, 3, 4 };
|
||||
|
@ -144,7 +151,7 @@ void JSONReflectorBoostHanaTests::testSerializeNestedObjects()
|
|||
CPPUNIT_ASSERT_EQUAL(
|
||||
"{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}}"s,
|
||||
string(nestingObj.toJson().GetString()));
|
||||
NestingArray nestingArray;
|
||||
NestingArrayHana nestingArray;
|
||||
nestingArray.name = "nesting2";
|
||||
nestingArray.testObjects.emplace_back(testObj);
|
||||
nestingArray.testObjects.emplace_back(testObj);
|
||||
|
@ -169,13 +176,14 @@ void JSONReflectorBoostHanaTests::testDeserializePrimitives()
|
|||
bool bool1 = false, bool2 = true;
|
||||
float float1 = 0.0;
|
||||
double double1 = 0.0;
|
||||
Reflector::pull(str1, array);
|
||||
Reflector::pull(int1, array);
|
||||
Reflector::pull(float1, array);
|
||||
Reflector::pull(str2, array);
|
||||
Reflector::pull(bool1, array);
|
||||
Reflector::pull(double1, array);
|
||||
Reflector::pull(bool2, array);
|
||||
JSONParseErrors errors;
|
||||
Reflector::pull(str1, array, &errors);
|
||||
Reflector::pull(int1, array, &errors);
|
||||
Reflector::pull(float1, array, &errors);
|
||||
Reflector::pull(str2, array, &errors);
|
||||
Reflector::pull(bool1, array, &errors);
|
||||
Reflector::pull(double1, array, &errors);
|
||||
Reflector::pull(bool2, array, &errors);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL("a"s, str1);
|
||||
CPPUNIT_ASSERT_EQUAL(5, int1);
|
||||
|
@ -191,8 +199,8 @@ void JSONReflectorBoostHanaTests::testDeserializePrimitives()
|
|||
*/
|
||||
void JSONReflectorBoostHanaTests::testDeserializeSimpleObjects()
|
||||
{
|
||||
const TestObject testObj(
|
||||
TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}"));
|
||||
const TestObjectHana testObj(
|
||||
TestObjectHana::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}"));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObj.number);
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
|
@ -206,9 +214,9 @@ void JSONReflectorBoostHanaTests::testDeserializeSimpleObjects()
|
|||
*/
|
||||
void JSONReflectorBoostHanaTests::testDeserializeNestedObjects()
|
||||
{
|
||||
const NestingObject nestingObj(NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}}"));
|
||||
const TestObject &testObj = nestingObj.testObj;
|
||||
const NestingObjectHana nestingObj(NestingObjectHana::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}}"));
|
||||
const TestObjectHana &testObj = nestingObj.testObj;
|
||||
CPPUNIT_ASSERT_EQUAL("nesting"s, nestingObj.name);
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObj.number);
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
|
@ -216,18 +224,97 @@ void JSONReflectorBoostHanaTests::testDeserializeNestedObjects()
|
|||
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean);
|
||||
|
||||
const NestingArray nestingArray(NestingArray::fromJson("{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false},{\"number\":43,\"number2\":3."
|
||||
"141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}]}"));
|
||||
const vector<TestObject> &testObjects = nestingArray.testObjects;
|
||||
const NestingArrayHana nestingArray(
|
||||
NestingArrayHana::fromJson("{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false},{\"number\":43,\"number2\":3."
|
||||
"141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}]}"));
|
||||
const vector<TestObjectHana> &testObjects = nestingArray.testObjects;
|
||||
CPPUNIT_ASSERT_EQUAL("nesting2"s, nestingArray.name);
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, testObjects.size());
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObjects[0].number);
|
||||
CPPUNIT_ASSERT_EQUAL(43, testObjects[1].number);
|
||||
for (const TestObject &testObj : testObjects) {
|
||||
for (const TestObjectHana &testObj : testObjects) {
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), testObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests whether JSONParseError is thrown on type mismatch.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testHandlingTypeMismatch()
|
||||
{
|
||||
JSONParseErrors errors;
|
||||
NestingArrayHana::fromJson("{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false},{\"number\":43,\"number2\":3."
|
||||
"141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}]}",
|
||||
&errors);
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
|
||||
|
||||
NestingObjectHana::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":\"42\",\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":"
|
||||
"\"test\",\"boolean\":false}}",
|
||||
&errors);
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors.front().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Number, errors.front().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors.front().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("number"s, string(errors.front().member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors.front().record));
|
||||
errors.clear();
|
||||
|
||||
NestingObjectHana::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":1,\"text\":"
|
||||
"\"test\",\"boolean\":false}}",
|
||||
&errors);
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors.front().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Array, errors.front().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Number, errors.front().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("numbers"s, string(errors.front().member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors.front().record));
|
||||
errors.clear();
|
||||
|
||||
NestingObjectHana::fromJson("{\"name\":[],\"testObj\":\"this is not an object\"}", &errors);
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors.front().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors.front().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Array, errors.front().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("name"s, string(errors.front().member));
|
||||
CPPUNIT_ASSERT_EQUAL("NestingObject"s, string(errors.front().record));
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors.back().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Object, errors.back().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors.back().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("testObj"s, string(errors.back().member));
|
||||
CPPUNIT_ASSERT_EQUAL("NestingObject"s, string(errors.back().record));
|
||||
errors.clear();
|
||||
|
||||
const NestingArrayHana nestingArray(
|
||||
NestingArrayHana::fromJson("{\"name\":\"nesting2\",\"testObjects\":[25,{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false},\"foo\",{\"number\":43,\"number2\":3."
|
||||
"141592653589793,\"numbers\":[1,2,3,4,\"bar\"],\"text\":\"test\",\"boolean\":false}]}",
|
||||
&errors));
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors[0].kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Object, errors[0].expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Number, errors[0].actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("testObjects"s, string(errors[0].member));
|
||||
CPPUNIT_ASSERT_EQUAL("NestingArray"s, string(errors[0].record));
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, errors[0].index);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors[1].kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Object, errors[1].expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors[1].actualType);
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, errors[1].index);
|
||||
CPPUNIT_ASSERT_EQUAL("testObjects"s, string(errors[1].member));
|
||||
CPPUNIT_ASSERT_EQUAL("NestingArray"s, string(errors[1].record));
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors[2].kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Number, errors[2].expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors[2].actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("numbers"s, string(errors[2].member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors[2].record));
|
||||
CPPUNIT_ASSERT_EQUAL(4_st, errors[2].index);
|
||||
errors.clear();
|
||||
|
||||
errors.throwOn = JSONParseErrors::ThrowOn::TypeMismatch;
|
||||
CPPUNIT_ASSERT_THROW(NestingObjectHana::fromJson("{\"name\":[],\"testObj\":\"this is not an object\"}", &errors), JSONParseError);
|
||||
}
|
||||
|
|
|
@ -73,28 +73,54 @@ template <> inline void push<NestingArray>(const NestingArray &reflectable, Valu
|
|||
push(reflectable.testObjects, "testObjects", value, allocator);
|
||||
}
|
||||
|
||||
template <> inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value)
|
||||
template <> inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JSONParseErrors *errors)
|
||||
{
|
||||
pull(reflectable.number, "number", value);
|
||||
pull(reflectable.number2, "number2", value);
|
||||
pull(reflectable.numbers, "numbers", value);
|
||||
pull(reflectable.text, "text", value);
|
||||
pull(reflectable.boolean, "boolean", value);
|
||||
const char *previousRecord;
|
||||
if (errors) {
|
||||
previousRecord = errors->currentRecord;
|
||||
errors->currentRecord = "TestObject";
|
||||
}
|
||||
pull(reflectable.number, "number", value, errors);
|
||||
pull(reflectable.number2, "number2", value, errors);
|
||||
pull(reflectable.numbers, "numbers", value, errors);
|
||||
pull(reflectable.text, "text", value, errors);
|
||||
pull(reflectable.boolean, "boolean", value, errors);
|
||||
if (errors) {
|
||||
errors->currentRecord = previousRecord;
|
||||
}
|
||||
}
|
||||
|
||||
template <> inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value)
|
||||
template <> inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JSONParseErrors *errors)
|
||||
{
|
||||
pull(reflectable.name, "name", value);
|
||||
pull(reflectable.testObj, "testObj", value);
|
||||
const char *previousRecord;
|
||||
if (errors) {
|
||||
previousRecord = errors->currentRecord;
|
||||
errors->currentRecord = "NestingObject";
|
||||
}
|
||||
pull(reflectable.name, "name", value, errors);
|
||||
pull(reflectable.testObj, "testObj", value, errors);
|
||||
if (errors) {
|
||||
errors->currentRecord = previousRecord;
|
||||
}
|
||||
}
|
||||
|
||||
template <> inline void pull<NestingArray>(NestingArray &reflectable, const GenericValue<UTF8<char>>::ConstObject &value)
|
||||
template <> inline void pull<NestingArray>(NestingArray &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JSONParseErrors *errors)
|
||||
{
|
||||
pull(reflectable.name, "name", value);
|
||||
pull(reflectable.testObjects, "testObjects", value);
|
||||
const char *previousRecord;
|
||||
if (errors) {
|
||||
previousRecord = errors->currentRecord;
|
||||
errors->currentRecord = "NestingArray";
|
||||
}
|
||||
pull(reflectable.name, "name", value, errors);
|
||||
pull(reflectable.testObjects, "testObjects", value, errors);
|
||||
if (errors) {
|
||||
errors->currentRecord = previousRecord;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Reflector
|
||||
|
||||
// namespace Reflector
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
/// \endcond
|
||||
|
@ -257,13 +283,14 @@ void JSONReflectorTests::testDeserializePrimitives()
|
|||
bool bool1 = false, bool2 = true;
|
||||
float float1 = 0.0;
|
||||
double double1 = 0.0;
|
||||
Reflector::pull(str1, array);
|
||||
Reflector::pull(int1, array);
|
||||
Reflector::pull(float1, array);
|
||||
Reflector::pull(str2, array);
|
||||
Reflector::pull(bool1, array);
|
||||
Reflector::pull(double1, array);
|
||||
Reflector::pull(bool2, array);
|
||||
JSONParseErrors errors;
|
||||
Reflector::pull(str1, array, &errors);
|
||||
Reflector::pull(int1, array, &errors);
|
||||
Reflector::pull(float1, array, &errors);
|
||||
Reflector::pull(str2, array, &errors);
|
||||
Reflector::pull(bool1, array, &errors);
|
||||
Reflector::pull(double1, array, &errors);
|
||||
Reflector::pull(bool2, array, &errors);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL("a"s, str1);
|
||||
CPPUNIT_ASSERT_EQUAL(5, int1);
|
||||
|
@ -320,6 +347,9 @@ void JSONReflectorTests::testDeserializeNestedObjects()
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON().
|
||||
*/
|
||||
void JSONReflectorTests::testHandlingParseError()
|
||||
{
|
||||
try {
|
||||
|
@ -332,13 +362,80 @@ void JSONReflectorTests::testHandlingParseError()
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests whether JSONParseError is thrown on type mismatch.
|
||||
*/
|
||||
void JSONReflectorTests::testHandlingTypeMismatch()
|
||||
{
|
||||
JSONParseErrors errors;
|
||||
NestingArray::fromJson("{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false},{\"number\":43,\"number2\":3."
|
||||
"141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}]}",
|
||||
&errors);
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
|
||||
|
||||
NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":\"42\",\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":"
|
||||
"\"test\",\"boolean\":false}}");
|
||||
"\"test\",\"boolean\":false}}",
|
||||
&errors);
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors.front().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Number, errors.front().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors.front().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("number"s, string(errors.front().member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors.front().record));
|
||||
errors.clear();
|
||||
|
||||
NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":1,\"text\":"
|
||||
"\"test\",\"boolean\":false}}");
|
||||
NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":\"this is not an object\"}");
|
||||
NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":\"42\",\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":"
|
||||
"\"test\",\"boolean\":\"false\"}}");
|
||||
"\"test\",\"boolean\":false}}",
|
||||
&errors);
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors.front().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Array, errors.front().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Number, errors.front().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("numbers"s, string(errors.front().member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors.front().record));
|
||||
errors.clear();
|
||||
|
||||
NestingObject::fromJson("{\"name\":[],\"testObj\":\"this is not an object\"}", &errors);
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors.front().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors.front().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Array, errors.front().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("name"s, string(errors.front().member));
|
||||
CPPUNIT_ASSERT_EQUAL("NestingObject"s, string(errors.front().record));
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors.back().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Object, errors.back().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors.back().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("testObj"s, string(errors.back().member));
|
||||
CPPUNIT_ASSERT_EQUAL("NestingObject"s, string(errors.back().record));
|
||||
errors.clear();
|
||||
|
||||
const NestingArray nestingArray(
|
||||
NestingArray::fromJson("{\"name\":\"nesting2\",\"testObjects\":[25,{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false},\"foo\",{\"number\":43,\"number2\":3."
|
||||
"141592653589793,\"numbers\":[1,2,3,4,\"bar\"],\"text\":\"test\",\"boolean\":false}]}",
|
||||
&errors));
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors[0].kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Object, errors[0].expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Number, errors[0].actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("testObjects"s, string(errors[0].member));
|
||||
CPPUNIT_ASSERT_EQUAL("NestingArray"s, string(errors[0].record));
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, errors[0].index);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors[1].kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Object, errors[1].expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors[1].actualType);
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, errors[1].index);
|
||||
CPPUNIT_ASSERT_EQUAL("testObjects"s, string(errors[1].member));
|
||||
CPPUNIT_ASSERT_EQUAL("NestingArray"s, string(errors[1].record));
|
||||
CPPUNIT_ASSERT_EQUAL(JSONParseErrorKind::TypeMismatch, errors[2].kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::Number, errors[2].expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JSONType::String, errors[2].actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("numbers"s, string(errors[2].member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors[2].record));
|
||||
CPPUNIT_ASSERT_EQUAL(4_st, errors[2].index);
|
||||
errors.clear();
|
||||
|
||||
errors.throwOn = JSONParseErrors::ThrowOn::TypeMismatch;
|
||||
CPPUNIT_ASSERT_THROW(NestingObject::fromJson("{\"name\":[],\"testObj\":\"this is not an object\"}", &errors), JSONParseError);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue