diff --git a/README.md b/README.md index bf14558..637d53c 100644 --- a/README.md +++ b/README.md @@ -49,22 +49,22 @@ For a full list of further ideas, see [TODOs.md](./TODOs.md). ## 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 | -| iteratable lists (`std::vector`, `std::list`, ...) | array | -| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array | -| `std::tuple` | array | -| `std::unique_ptr`, `std::shared_ptr` | depends/null | -| `std::map`, `std::unordered_map` | object | -| `std::variant` | object | -| `JsonSerializable` | object | +| 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 | +| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array | +| `std::tuple` | array | +| `std::unique_ptr`, `std::shared_ptr` | depends/null | +| `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` | object | +| `std::variant` | object | +| `JsonSerializable` | object | ### Remarks * Raw pointer are not supported. This prevents @@ -85,7 +85,11 @@ The following table shows the mapping of supported C++ types to supported JSON t * It is possible to treat custom types as set/map using the macro `REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH`, `REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH`, `REFLECTIVE_RAPIDJSON_TREAT_AS_SET` or `REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET`. -* The key type of `std::map` and `std::unordered_map` must be `std::string`. +* The key type of `std::map`, `std::unordered_map`, `std::multimap` and `std::unordered_multimap` must be + `std::string`. +* An array is used to represent the multiple values of an `std::multimap` and `std::unordered_multimap` (for + consistency also when there is only one value present). This is because the JSON RFC says that + "The names within an object SHOULD be unique". * An `std::variant` is represented by an object like `{"index": ..., "data": ...}` where `index` is the zero-based index of the alternative held by the variant and `data` the value held by the variant. The type of `data` is `null` for `std::monostate` and otherwise deduced as usual. diff --git a/lib/json/reflector.h b/lib/json/reflector.h index 75979eb..e972767 100644 --- a/lib/json/reflector.h +++ b/lib/json/reflector.h @@ -17,9 +17,13 @@ #include #include +#include #include +#include #include #include +#include +#include #include #include "./errorhandling.h" @@ -213,10 +217,9 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_ } /*! - * \brief Pushes the specified map (std::map, std::unordered_map) or multimap (std::multimap, std::unordered_multimap) to the - * specified value. + * \brief Pushes the specified map (std::map, std::unordered_map) to the specified value. */ -template , IsMultiMapOrHash> * = nullptr> +template > * = nullptr> void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) { value.SetObject(); @@ -226,6 +229,27 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_ } } +/*! + * \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) { + auto arrayValue = RAPIDJSON_NAMESPACE::Value(RAPIDJSON_NAMESPACE::Type::kArrayType); + const auto memberName = RAPIDJSON_NAMESPACE::Value::StringRefType(item.first.data(), rapidJsonSize(item.first.size())); + const auto existingMember = value.FindMember(memberName); + if (existingMember != value.MemberEnd() && existingMember->value.GetType() == RAPIDJSON_NAMESPACE::Type::kArrayType) { + arrayValue = existingMember->value; + } else { + value.AddMember(memberName, arrayValue, allocator); + } + RAPIDJSON_NAMESPACE::Value::Array array = arrayValue.GetArray(); + push(item.second, array, allocator); + } +} + namespace Detail { /*! @@ -686,8 +710,16 @@ void pull(Type &reflectable, const rapidjson::GenericValuename.GetString(), typename Type::mapped_type())); - pull(insertedIterator->second, i->value, errors); + 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 &value : array) { + auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type())); + pull(insertedIterator->second, value, errors); + } } } diff --git a/lib/tests/jsonreflector.cpp b/lib/tests/jsonreflector.cpp index 9de2409..8414f24 100644 --- a/lib/tests/jsonreflector.cpp +++ b/lib/tests/jsonreflector.cpp @@ -52,6 +52,8 @@ struct TestObject : public JsonSerializable { bool boolean; map someMap; unordered_map someHash; + multimap someMultimap; + unordered_multimap someMultiHash; set someSet; multiset someMultiset; unordered_set someUnorderedSet; @@ -84,6 +86,8 @@ template <> inline void push(const TestObject &reflectable, Value::O push(reflectable.boolean, "boolean", value, allocator); push(reflectable.someMap, "someMap", value, allocator); push(reflectable.someHash, "someHash", value, allocator); + push(reflectable.someMultimap, "someMultimap", value, allocator); + push(reflectable.someMultiHash, "someMultiHash", value, allocator); push(reflectable.someSet, "someSet", value, allocator); push(reflectable.someMultiset, "someMultiset", value, allocator); push(reflectable.someUnorderedSet, "someUnorderedSet", value, allocator); @@ -120,6 +124,8 @@ inline void pull(TestObject &reflectable, const GenericValue nestedInVector; nestedInVector.emplace_back(testObj); CPPUNIT_ASSERT_EQUAL( - "[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, + "[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, string(JsonReflector::toJson(nestedInVector).GetString())); } @@ -339,7 +347,7 @@ void JsonReflectorTests::testSerializeUniquePtr() Writer jsonWriter(strbuf); doc.Accept(jsonWriter); CPPUNIT_ASSERT_EQUAL( - "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, + "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, string(strbuf.GetString())); } @@ -367,7 +375,7 @@ void JsonReflectorTests::testSerializeSharedPtr() Writer jsonWriter(strbuf); doc.Accept(jsonWriter); CPPUNIT_ASSERT_EQUAL( - "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, + "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, string(strbuf.GetString())); } @@ -432,11 +440,13 @@ 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,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"c\":true,\"d\":false},\"someSet\":[\"a\",\"b\"],\"someMultiset\":[" - "\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"b\"],\"someUnorderedMultiset\":[\"a\",\"a\"],\"someVariant\":{\"index\":0," - "\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"foo\"},\"yetAnotherVariant\":{\"index\":1,\"data\":42}}")); + const auto 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},\"someMultimap\":{\"a\":[1,2],\"b\":[3]}," + "\"someMultiHash\":{\"a\":[4,5],\"b\":[6]}," + "\"someSet\":[\"a\",\"b\"],\"someMultiset\":[" + "\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"b\"],\"someUnorderedMultiset\":[\"a\",\"a\"],\"someVariant\":{\"index\":0," + "\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"foo\"},\"yetAnotherVariant\":{\"index\":1,\"data\":42}}"); CPPUNIT_ASSERT_EQUAL(42, testObj.number); CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2); @@ -447,6 +457,10 @@ void JsonReflectorTests::testDeserializeSimpleObjects() CPPUNIT_ASSERT_EQUAL(expectedMap, testObj.someMap); const unordered_map expectedHash{ { "c", true }, { "d", false } }; CPPUNIT_ASSERT_EQUAL(expectedHash, testObj.someHash); + const multimap expectedMultiMap{ { "a", 1 }, { "a", 2 }, { "b", 3 } }; + CPPUNIT_ASSERT_EQUAL(expectedMultiMap, testObj.someMultimap); + const unordered_multimap expectedUnorderedMultiMap{ { "a", 4 }, { "a", 5 }, { "b", 6 } }; + CPPUNIT_ASSERT_EQUAL(expectedUnorderedMultiMap, testObj.someMultiHash); CPPUNIT_ASSERT_EQUAL(set({ "a", "b" }), testObj.someSet); CPPUNIT_ASSERT_EQUAL(multiset({ "a", "a" }), testObj.someMultiset); CPPUNIT_ASSERT_EQUAL(unordered_set({ "a", "b" }), testObj.someUnorderedSet); diff --git a/lib/traits.h b/lib/traits.h index 5c792ca..aa921a0 100644 --- a/lib/traits.h +++ b/lib/traits.h @@ -59,7 +59,7 @@ using IsArrayOrSet = Traits::Any, Traits: TreatAsSet, TreatAsMultiSet>; template using IsArray = Traits::All, Traits::Not>, - Traits::Not>, Traits::Not>, Traits::Not>>; + Traits::Not>, Traits::Not>, Traits::Not>, Traits::Not>>; template using IsIteratableExceptString = Traits::All, Traits::Not>>; template using IsVariant = Traits::All>;