Support std::map and std::unordered_map

This commit is contained in:
Martchus 2017-11-13 20:16:43 +01:00
parent 19590d30a4
commit a7f587cb84
5 changed files with 103 additions and 36 deletions

View File

@ -13,18 +13,20 @@ generator.
## Supported datatypes
The following table shows the mapping of supported C++ types to supported JSON types:
| C++ type | JSON type |
| -------------------------------------------- |:------------:|
| custom structures/classes | object |
| `bool` | true/false |
| signed and unsigned integral types | number |
| `float` and `double` | number |
| `enum` and `enum class` | number |
| `std::string` | string |
| `const char *` | string |
| iteratables (`std::vector`, `std::list`, ...)| array |
| `std::tuple` | array |
| `std::unique_ptr`, `std::shared_ptr` | depends/null |
| C++ type | JSON type |
| ------------------------------------------------- |:------------:|
| custom structures/classes | object |
| `bool` | true/false |
| signed and unsigned integral types | number |
| `float` and `double` | number |
| `enum` and `enum class` | number |
| `std::string` | string |
| `const char *` | string |
| iteratable lists (`std::vector`, `std::list`, ...)| array |
| `std::tuple` | array |
| `std::unique_ptr`, `std::shared_ptr` | depends/null |
| `std::map`, `std::unordered_map` | object |
| `JsonSerializable` | object |
### Remarks
* Raw pointer are not supported. This prevents
@ -34,7 +36,7 @@ The following table shows the mapping of supported C++ types to supported JSON t
* For deserialization, iteratables must provide an `emplace_back` method. So deserialization of eg. `std::forward_list`
is currently not supported.
* The JSON type for smart pointer depends on the type the pointer refers to. It can also be `null`.
* See also TODOs.md.
* For custom (de)serialization, see the section below.
## Usage
This example shows how the library can be used to make a `struct` serializable:

View File

@ -19,6 +19,6 @@
## Library-only
- [x] Support `std::unique_ptr` and `std::shared_ptr`
- [ ] Support `std::map` and `std::unordered_map`
- [x] Support `std::map` and `std::unordered_map`
- [ ] Support `std::any`
- [x] Support/document customized (de)serialization (eg. serialize some `DateTime` object to ISO string representation)

View File

@ -59,14 +59,20 @@ template <typename Type, Traits::EnableIfAny<Traits::IsString<Type>, Traits::IsC
return JsonType::String;
}
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsString<Type>>>...> constexpr JsonType jsonType()
template <typename Type,
Traits::EnableIf<Traits::IsIteratable<Type>,
Traits::Not<Traits::Any<Traits::IsString<Type>, Traits::IsSpecializationOf<Type, std::map>,
Traits::IsSpecializationOf<Type, std::unordered_map>>>>...>
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>>...>
Traits::All<Traits::IsIteratable<Type>,
Traits::Not<Traits::Any<Traits::IsString<Type>, Traits::IsSpecializationOf<Type, std::map>,
Traits::IsSpecializationOf<Type, std::unordered_map>>>>>...>
constexpr JsonType jsonType()
{
return JsonType::Object;

View File

@ -16,9 +16,11 @@
#include <rapidjson/writer.h>
#include <limits>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include "./errorhandling.h"
@ -75,6 +77,13 @@ template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
// define trait to check for custom structs/classes which are JSON serializable
template <typename Type> using IsJsonSerializable = Traits::Any<std::is_base_of<JsonSerializable<Type>, Type>, AdaptedJsonSerializable<Type>>;
// define trait to check for map or hash
template <typename Type>
using IsMapOrHash = Traits::Any<Traits::IsSpecializationOf<Type, std::map>, Traits::IsSpecializationOf<Type, std::unordered_map>>;
template <typename Type>
using IsArray
= Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>, Traits::Not<IsMapOrHash<Type>>>;
// define functions to "push" values to a RapidJSON array or object
/*!
@ -175,8 +184,7 @@ inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAP
/*!
* \brief Pushes the specified iteratable (eg. std::vector, std::list) to the specified value.
*/
template <typename Type,
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::HasSize<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::HasSize<Type>>...>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.SetArray();
@ -188,11 +196,9 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
}
/*!
* \brief Pushes the specified iteratable (eg. std::vector, std::list) to the specified value.
* \brief Pushes the specified iteratable list (eg. std::vector, std::list) to the specified value.
*/
template <typename Type,
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::HasSize<Type>>,
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::Not<Traits::HasSize<Type>>>...>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.SetArray();
@ -202,6 +208,19 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
}
}
/*!
* \brief Pushes the specified map (std::map, std::unordered_map) to the specified value.
*/
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>>...>
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);
}
}
namespace Detail {
/*!
@ -349,15 +368,13 @@ inline void pull(
/*!
* \brief Pulls the speciified \a reflectable which is an iteratable from the specified array. The \a reflectable is cleared before.
*/
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
template <typename Type, Traits::EnableIf<IsArray<Type>>...>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors);
/*!
* \brief Pulls the speciified \a reflectable which is an iteratable without reserve() method from the specified value which is checked to contain an array.
*/
template <typename Type,
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsReservable<Type>>,
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::Not<Traits::IsReservable<Type>>>...>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (!value.IsArray()) {
@ -372,8 +389,7 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
/*!
* \brief Pulls the speciified \a reflectable which is an iteratable with reserve() method from the specified value which is checked to contain an array.
*/
template <typename Type,
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::IsReservable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsReservable<Type>>...>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (!value.IsArray()) {
@ -390,7 +406,7 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
/*!
* \brief Pulls the speciified \a reflectable which is an iteratable from the specified array. The \a reflectable is cleared before.
*/
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
template <typename Type, Traits::EnableIf<IsArray<Type>>...>
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors)
{
// clear previous contents of the array
@ -414,6 +430,24 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
}
}
/*!
* \brief Pulls the speciified \a reflectable which is a map from the specified value which is checked to contain an object.
*/
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>>...>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (!value.IsObject()) {
if (errors) {
errors->reportTypeMismatch<Type>(value.GetType());
}
return;
}
auto obj = value.GetObject();
for (auto i = obj.MemberBegin(), end = obj.MemberEnd(); i != end; ++i) {
pull(reflectable[i->name.GetString()], i->value, errors);
}
}
namespace Detail {
/*!

View File

@ -15,8 +15,10 @@ using TestUtilities::operator<<; // must be visible prior to the call site
#include <rapidjson/writer.h>
#include <iostream>
#include <map>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
using namespace std;
@ -28,6 +30,14 @@ using namespace TestUtilities;
using namespace TestUtilities::Literals;
using namespace ReflectiveRapidJSON;
// test traits
static_assert(JsonReflector::IsArray<vector<int>>::value, "vector mapped to array");
static_assert(JsonReflector::IsArray<list<int>>::value, "list mapped to array");
static_assert(!JsonReflector::IsArray<string>::value, "string mapped to string");
static_assert(JsonReflector::IsMapOrHash<map<string, int>>::value, "map mapped to object");
static_assert(JsonReflector::IsMapOrHash<unordered_map<string, int>>::value, "hash mapped to object");
static_assert(!JsonReflector::IsMapOrHash<vector<int>>::value, "vector not mapped to object");
/// \cond
// define some structs for testing serialization
@ -37,6 +47,8 @@ struct TestObject : public JsonSerializable<TestObject> {
vector<int> numbers;
string text;
bool boolean;
map<string, int> someMap;
unordered_map<string, bool> someHash;
};
struct NestingObject : public JsonSerializable<NestingObject> {
@ -72,6 +84,8 @@ template <> inline void push<TestObject>(const TestObject &reflectable, Value::O
push(reflectable.numbers, "numbers", value, allocator);
push(reflectable.text, "text", value, allocator);
push(reflectable.boolean, "boolean", value, allocator);
push(reflectable.someMap, "someMap", value, allocator);
push(reflectable.someHash, "someHash", value, allocator);
}
template <> inline void push<NestingObject>(const NestingObject &reflectable, Value::Object &value, Document::AllocatorType &allocator)
@ -99,6 +113,8 @@ inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<ch
pull(reflectable.numbers, "numbers", value, errors);
pull(reflectable.text, "text", value, errors);
pull(reflectable.boolean, "boolean", value, errors);
pull(reflectable.someMap, "someMap", value, errors);
pull(reflectable.someHash, "someHash", value, errors);
if (errors) {
errors->currentRecord = previousRecord;
}
@ -240,7 +256,10 @@ void JsonReflectorTests::testSerializeSimpleObjects()
testObj.numbers = { 1, 2, 3, 4 };
testObj.text = "test";
testObj.boolean = false;
CPPUNIT_ASSERT_EQUAL("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}"s,
testObj.someMap = { { "a", 1 }, { "b", 2 } };
testObj.someHash = { { "c", true }, { "d", false } };
CPPUNIT_ASSERT_EQUAL(
"{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"d\":false,\"c\":true}}"s,
string(testObj.toJson().GetString()));
}
@ -258,7 +277,7 @@ void JsonReflectorTests::testSerializeNestedObjects()
testObj.text = "test";
testObj.boolean = false;
CPPUNIT_ASSERT_EQUAL(
"{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}}"s,
"{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{}}}"s,
string(nestingObj.toJson().GetString()));
NestingArray nestingArray;
nestingArray.name = "nesting2";
@ -266,7 +285,7 @@ void JsonReflectorTests::testSerializeNestedObjects()
nestingArray.testObjects.emplace_back(testObj);
nestingArray.testObjects.back().number = 43;
CPPUNIT_ASSERT_EQUAL(
"{\"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}]}"s,
"{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{}},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{}}]}"s,
string(nestingArray.toJson().GetString()));
}
@ -293,7 +312,8 @@ void JsonReflectorTests::testSerializeUniquePtr()
StringBuffer strbuf;
Writer<StringBuffer> jsonWriter(strbuf);
doc.Accept(jsonWriter);
CPPUNIT_ASSERT_EQUAL("[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false}]"s,
CPPUNIT_ASSERT_EQUAL(
"[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{}}]"s,
string(strbuf.GetString()));
}
@ -320,7 +340,8 @@ void JsonReflectorTests::testSerializeSharedPtr()
StringBuffer strbuf;
Writer<StringBuffer> jsonWriter(strbuf);
doc.Accept(jsonWriter);
CPPUNIT_ASSERT_EQUAL("[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false}]"s,
CPPUNIT_ASSERT_EQUAL(
"[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{}}]"s,
string(strbuf.GetString()));
}
@ -379,14 +400,18 @@ void JsonReflectorTests::testDeserializePrimitives()
*/
void JsonReflectorTests::testDeserializeSimpleObjects()
{
const TestObject testObj(
TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}"));
const TestObject testObj(TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":"
"false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"c\":true,\"d\":false}}"));
CPPUNIT_ASSERT_EQUAL(42, testObj.number);
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);
const map<string, int> expectedMap{ { "a", 1 }, { "b", 2 } };
CPPUNIT_ASSERT_EQUAL(expectedMap, testObj.someMap);
const unordered_map<string, bool> expectedHash{ { "c", true }, { "d", false } };
CPPUNIT_ASSERT_EQUAL(expectedHash, testObj.someHash);
}
/*!