From 315ffff9e7d3ece7f14b0e1bd5d49fc27f186b7c Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Thu, 16 Feb 2023 15:09:45 -0800 Subject: [PATCH] Fix `any`, embedded `object`, `array`, and struct serialization (#94) * add embedded object serialization tests (failing) * Add empty object tests (failing) * test for deserializing null field object (failing) * Fix embedded object deserialization * Fix serializing empty objects on nlohmann * Fix nullptr_t handling in `any` * Add test for de/serializing struct embedded in `object` * Remove extraneous `get()` from `any` * fix compiler errors and warnings on gcc * add `any = dap::null` assignment test * Remove extraneous template --- include/dap/any.h | 13 +++ include/dap/serialization.h | 10 ++ src/any_test.cpp | 47 +++++++-- src/json_serializer_test.cpp | 170 ++++++++++++++++++++++++++++++- src/nlohmann_json_serializer.cpp | 25 ++++- src/rapid_json_serializer.cpp | 21 ++++ 6 files changed, 273 insertions(+), 13 deletions(-) diff --git a/include/dap/any.h b/include/dap/any.h index 187ca5d..4a6444c 100644 --- a/include/dap/any.h +++ b/include/dap/any.h @@ -23,6 +23,8 @@ namespace dap { template struct TypeOf; +class Deserializer; +class Serializer; // any provides a type-safe container for values of any of dap type (boolean, // integer, number, array, variant, any, null, dap-structs). @@ -47,6 +49,7 @@ class any { inline any& operator=(any&& rhs) noexcept; template inline any& operator=(const T& val); + inline any& operator=(const std::nullptr_t& val); // get() returns the contained value of the type T. // If the any does not contain a value of type T, then get() will assert. @@ -58,6 +61,9 @@ class any { inline bool is() const; private: + friend class Deserializer; + friend class Serializer; + static inline void* alignUp(void* val, size_t alignment); inline void alloc(size_t size, size_t align); inline void free(); @@ -142,8 +148,15 @@ any& any::operator=(const T& val) { return *this; } +any& any::operator=(const std::nullptr_t&) { + reset(); + return *this; +} + template T& any::get() const { + static_assert(!std::is_same(), + "Cannot get nullptr from 'any'."); assert(is()); return *reinterpret_cast(value); } diff --git a/include/dap/serialization.h b/include/dap/serialization.h index ee4fd87..c7d4c5e 100644 --- a/include/dap/serialization.h +++ b/include/dap/serialization.h @@ -177,8 +177,18 @@ class Serializer { // deserialize() encodes the given string. inline bool serialize(const char* v); + protected: + static inline const TypeInfo* get_any_type(const any&); + static inline const void* get_any_val(const any&); }; +inline const TypeInfo* Serializer::get_any_type(const any& a){ + return a.type; +} +const void* Serializer::get_any_val(const any& a) { + return a.value; +} + template bool Serializer::serialize(const T& object) { return TypeOf::type()->serialize(this, &object); diff --git a/src/any_test.cpp b/src/any_test.cpp index ab1ebb7..7dfb73c 100644 --- a/src/any_test.cpp +++ b/src/any_test.cpp @@ -42,6 +42,10 @@ namespace { template struct TestValue {}; +template <> +struct TestValue { + static const dap::null value; +}; template <> struct TestValue { static const dap::integer value; @@ -67,6 +71,7 @@ struct TestValue { static const dap::AnyTestObject value; }; +const dap::null TestValue::value = nullptr; const dap::integer TestValue::value = 20; const dap::boolean TestValue::value = true; const dap::number TestValue::value = 123.45; @@ -152,7 +157,24 @@ TEST(Any, TestObject) { template class AnyT : public ::testing::Test { protected: - void check(const dap::any& any, const T& expect) { + template ::value && + !std::is_same::value>> + void check_val(const dap::any& any, const T0& expect) { + ASSERT_EQ(any.is(), any.is()); + ASSERT_EQ(any.get(), expect); + } + + // Special case for Null assignment, as we can assign nullptr_t to any but + // can't `get()` it + template + void check_val(const dap::any& any, const dap::null& expect) { + ASSERT_EQ(nullptr, expect); + ASSERT_TRUE(any.is()); + } + + void check_type(const dap::any& any) { + ASSERT_EQ(any.is(), (std::is_same::value)); ASSERT_EQ(any.is(), (std::is_same::value)); ASSERT_EQ(any.is(), (std::is_same::value)); ASSERT_EQ(any.is(), (std::is_same::value)); @@ -161,8 +183,6 @@ class AnyT : public ::testing::Test { (std::is_same>::value)); ASSERT_EQ(any.is(), (std::is_same::value)); - - ASSERT_EQ(any.get(), expect); } }; TYPED_TEST_SUITE_P(AnyT); @@ -170,27 +190,31 @@ TYPED_TEST_SUITE_P(AnyT); TYPED_TEST_P(AnyT, CopyConstruct) { auto val = TestValue::value; dap::any any(val); - this->check(any, val); + this->check_type(any); + this->check_val(any, val); } TYPED_TEST_P(AnyT, MoveConstruct) { auto val = TestValue::value; dap::any any(std::move(val)); - this->check(any, val); + this->check_type(any); + this->check_val(any, val); } TYPED_TEST_P(AnyT, Assign) { auto val = TestValue::value; dap::any any; any = val; - this->check(any, val); + this->check_type(any); + this->check_val(any, val); } TYPED_TEST_P(AnyT, MoveAssign) { auto val = TestValue::value; dap::any any; any = std::move(val); - this->check(any, val); + this->check_type(any); + this->check_val(any, val); } TYPED_TEST_P(AnyT, RepeatedAssign) { @@ -199,7 +223,8 @@ TYPED_TEST_P(AnyT, RepeatedAssign) { dap::any any; any = str; any = val; - this->check(any, val); + this->check_type(any); + this->check_val(any, val); } TYPED_TEST_P(AnyT, RepeatedMoveAssign) { @@ -208,7 +233,8 @@ TYPED_TEST_P(AnyT, RepeatedMoveAssign) { dap::any any; any = std::move(str); any = std::move(val); - this->check(any, val); + this->check_type(any); + this->check_val(any, val); } REGISTER_TYPED_TEST_SUITE_P(AnyT, @@ -219,7 +245,8 @@ REGISTER_TYPED_TEST_SUITE_P(AnyT, RepeatedAssign, RepeatedMoveAssign); -using AnyTypes = ::testing::Types()); + ASSERT_TRUE(obj.at("two").is()); + ASSERT_TRUE(obj.at("three").is()); + ASSERT_TRUE(obj.at("four").is()); + + ASSERT_EQ(ref_obj.at("one").get(), + obj.at("one").get()); + ASSERT_EQ(ref_obj.at("two").get(), + obj.at("two").get()); + ASSERT_EQ(ref_obj.at("three").get(), + obj.at("three").get()); + ASSERT_EQ(ref_obj.at("four").get(), + obj.at("four").get()); + NESTED_TEST_FAILED = false; + } + template + void TEST_SERIALIZING_DESERIALIZING(const T& encoded, T& decoded) { + NESTED_TEST_FAILED = true; + dap::json::Serializer s; + ASSERT_TRUE(s.serialize(encoded)); + dap::json::Deserializer d(s.dump()); + ASSERT_TRUE(d.deserialize(&decoded)); + NESTED_TEST_FAILED = false; + } + bool NESTED_TEST_FAILED = false; +#define _ASSERT_PASS(NESTED_TEST) \ + NESTED_TEST; \ + ASSERT_FALSE(NESTED_TEST_FAILED); +}; + +TEST_F(JSONSerializer, SerializeDeserialize) { dap::JSONTestObject encoded; encoded.b = true; encoded.i = 32; @@ -92,9 +143,124 @@ TEST(JSONSerializer, SerializeDeserialize) { ASSERT_EQ(encoded.inner.i, decoded.inner.i); } -TEST(JSONSerializer, SerializeObjectNoFields) { +TEST_F(JSONSerializer, SerializeObjectNoFields) { dap::JSONObjectNoFields obj; dap::json::Serializer s; ASSERT_TRUE(s.serialize(obj)); ASSERT_EQ(s.dump(), "{}"); } + +TEST_F(JSONSerializer, SerializeDeserializeObject) { + dap::object encoded = GetSimpleObject(); + dap::object decoded; + _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded)); + _ASSERT_PASS(TEST_SIMPLE_OBJECT(decoded)); +} + +TEST_F(JSONSerializer, SerializeDeserializeEmbeddedObject) { + dap::object encoded; + dap::object decoded; + // object nested inside object + dap::object encoded_embed_obj = GetSimpleObject(); + dap::object decoded_embed_obj; + encoded["embed_obj"] = encoded_embed_obj; + _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded)); + ASSERT_TRUE(decoded["embed_obj"].is()); + decoded_embed_obj = decoded["embed_obj"].get(); + _ASSERT_PASS(TEST_SIMPLE_OBJECT(decoded_embed_obj)); +} + +TEST_F(JSONSerializer, SerializeDeserializeEmbeddedStruct) { + dap::object encoded; + dap::object decoded; + // object nested inside object + dap::SimpleJSONTestObject encoded_embed_struct; + encoded_embed_struct.b = true; + encoded_embed_struct.i = 50; + encoded["embed_struct"] = encoded_embed_struct; + + dap::object decoded_embed_obj; + _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded)); + ASSERT_TRUE(decoded["embed_struct"].is()); + decoded_embed_obj = decoded["embed_struct"].get(); + ASSERT_TRUE(decoded_embed_obj.at("b").is()); + ASSERT_TRUE(decoded_embed_obj.at("i").is()); + + ASSERT_EQ(encoded_embed_struct.b, decoded_embed_obj["b"].get()); + ASSERT_EQ(encoded_embed_struct.i, decoded_embed_obj["i"].get()); +} + +TEST_F(JSONSerializer, SerializeDeserializeEmbeddedIntArray) { + dap::object encoded; + dap::object decoded; + // array nested inside object + dap::array encoded_embed_arr = {1, 2, 3, 4}; + dap::array decoded_embed_arr; + + encoded["embed_arr"] = encoded_embed_arr; + + _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded)); + // TODO: Deserializing array should infer basic member types + ASSERT_TRUE(decoded["embed_arr"].is>()); + decoded_embed_arr = decoded["embed_arr"].get>(); + ASSERT_EQ(encoded_embed_arr.size(), decoded_embed_arr.size()); + for (std::size_t i = 0; i < decoded_embed_arr.size(); i++) { + ASSERT_TRUE(decoded_embed_arr[i].is()); + ASSERT_EQ(encoded_embed_arr[i], decoded_embed_arr[i].get()); + } +} + +TEST_F(JSONSerializer, SerializeDeserializeEmbeddedObjectArray) { + dap::object encoded; + dap::object decoded; + + dap::array encoded_embed_arr = {GetSimpleObject(), + GetSimpleObject()}; + dap::array decoded_embed_arr; + + encoded["embed_arr"] = encoded_embed_arr; + + _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded)); + // TODO: Deserializing array should infer basic member types + ASSERT_TRUE(decoded["embed_arr"].is>()); + decoded_embed_arr = decoded["embed_arr"].get>(); + ASSERT_EQ(encoded_embed_arr.size(), decoded_embed_arr.size()); + for (std::size_t i = 0; i < decoded_embed_arr.size(); i++) { + ASSERT_TRUE(decoded_embed_arr[i].is()); + _ASSERT_PASS(TEST_SIMPLE_OBJECT(decoded_embed_arr[i].get())); + } +} + +TEST_F(JSONSerializer, DeserializeSerializeEmptyObject) { + auto empty_obj = "{}"; + dap::object decoded; + dap::json::Deserializer d(empty_obj); + ASSERT_TRUE(d.deserialize(&decoded)); + dap::json::Serializer s; + ASSERT_TRUE(s.serialize(decoded)); + ASSERT_EQ(s.dump(), empty_obj); +} + +TEST_F(JSONSerializer, SerializeDeserializeEmbeddedEmptyObject) { + dap::object encoded_empty_obj; + dap::object encoded = {{"empty_obj", encoded_empty_obj}}; + dap::object decoded; + + _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded)); + ASSERT_TRUE(decoded["empty_obj"].is()); + dap::object decoded_empty_obj = decoded["empty_obj"].get(); + ASSERT_EQ(encoded_empty_obj.size(), decoded_empty_obj.size()); +} + +TEST_F(JSONSerializer, SerializeDeserializeObjectWithNulledField) { + auto thing = dap::any(dap::null()); + dap::object encoded; + encoded["nulled_field"] = dap::null(); + dap::json::Serializer s; + ASSERT_TRUE(s.serialize(encoded)); + dap::object decoded; + auto dump = s.dump(); + dap::json::Deserializer d(dump); + ASSERT_TRUE(d.deserialize(&decoded)); + ASSERT_TRUE(encoded["nulled_field"].is()); +} diff --git a/src/nlohmann_json_serializer.cpp b/src/nlohmann_json_serializer.cpp index 3e9c847..7834230 100644 --- a/src/nlohmann_json_serializer.cpp +++ b/src/nlohmann_json_serializer.cpp @@ -91,6 +91,18 @@ bool NlohmannDeserializer::deserialize(dap::any* v) const { *v = dap::integer(json->get()); } else if (json->is_string()) { *v = json->get(); + } else if (json->is_object()) { + dap::object obj; + if (!deserialize(&obj)) { + return false; + } + *v = obj; + } else if (json->is_array()) { + dap::array arr; + if (!deserialize(&arr)) { + return false; + } + *v = arr; } else if (json->is_null()) { *v = null(); } else { @@ -169,6 +181,9 @@ bool NlohmannSerializer::serialize(const dap::string& v) { } bool NlohmannSerializer::serialize(const dap::object& v) { + if (!json->is_object()) { + *json = nlohmann::json::object(); + } for (auto& it : v) { NlohmannSerializer s(&(*json)[it.first]); if (!s.serialize(it.second)) { @@ -187,11 +202,19 @@ bool NlohmannSerializer::serialize(const dap::any& v) { *json = (double)v.get(); } else if (v.is()) { *json = v.get(); + } else if (v.is()) { + // reachable if dap::object nested is inside other dap::object + return serialize(v.get()); } else if (v.is()) { } else { + // reachable if array or custom serialized type is nested inside other + auto type = get_any_type(v); + auto value = get_any_val(v); + if (type && value) { + return type->serialize(this, value); + } return false; } - return true; } diff --git a/src/rapid_json_serializer.cpp b/src/rapid_json_serializer.cpp index 2eec75d..178db99 100644 --- a/src/rapid_json_serializer.cpp +++ b/src/rapid_json_serializer.cpp @@ -98,6 +98,18 @@ bool RapidDeserializer::deserialize(dap::any* v) const { *v = dap::string(json()->GetString()); } else if (json()->IsNull()) { *v = null(); + } else if (json()->IsObject()) { + dap::object obj; + if (!deserialize(&obj)) { + return false; + } + *v = obj; + } else if (json()->IsArray()){ + dap::array arr; + if (!deserialize(&arr)){ + return false; + } + *v = arr; } else { return false; } @@ -203,8 +215,17 @@ bool RapidSerializer::serialize(const dap::any& v) { } else if (v.is()) { auto s = v.get(); json()->SetString(s.data(), static_cast(s.length()), allocator); + } else if (v.is()) { + // reachable if dap::object nested is inside other dap::object + return serialize(v.get()); } else if (v.is()) { } else { + // reachable if array or custom serialized type is nested inside other dap::object + auto type = get_any_type(v); + auto value = get_any_val(v); + if (type && value) { + return type->serialize(this, value); + } return false; }