Serialization: Correctly encode structs with no fields
Empty structs were being serialized as `null`, when they should have been serialized as `{}`.
This was due to the type inference on the serializer - where no calls to `field()` would result in the default `null` type.
To solve this, the `serialize(const void* object, const std::initializer_list<Field>&)` inline helper has been promoted to a virtual function (and renamed to `fields()`).
The JSON serializer implementation of this now first sets the object type to `object`, even if there are no fields to serialize.
Added test for this.
Fixes: #10
diff --git a/include/dap/serialization.h b/include/dap/serialization.h
index 95fcb56..25f774e 100644
--- a/include/dap/serialization.h
+++ b/include/dap/serialization.h
@@ -166,6 +166,10 @@
// Serializer that should be used to encode the n'th array element's data.
virtual bool array(size_t count, const std::function<bool(Serializer*)>&) = 0;
+ // fields() encodes all the provided fields of the given object.
+ virtual bool fields(const void* object,
+ const std::initializer_list<Field>&) = 0;
+
// field() encodes a field to the struct object referenced by this Serializer.
// The FieldSerializer will be called with a Serializer used to encode the
// field's data.
@@ -192,10 +196,6 @@
template <typename T0, typename... Types>
inline bool serialize(const dap::variant<T0, Types...>&);
- // serialize() encodes all the provided fields of the given object.
- inline bool serialize(const void* object,
- const std::initializer_list<Field>&);
-
// deserialize() encodes the given string.
inline bool serialize(const char* v);
@@ -231,18 +231,6 @@
return serialize(var.value);
}
-bool Serializer::serialize(const void* object,
- const std::initializer_list<Field>& fields) {
- for (auto const& f : fields) {
- if (!field(f.name, [&](Serializer* d) {
- auto ptr = reinterpret_cast<const uint8_t*>(object) + f.offset;
- return f.type->serialize(d, ptr);
- }))
- return false;
- }
- return true;
-}
-
bool Serializer::serialize(const char* v) {
return serialize(std::string(v));
}
diff --git a/include/dap/typeof.h b/include/dap/typeof.h
index 3fd7cb0..e2d3cc8 100644
--- a/include/dap/typeof.h
+++ b/include/dap/typeof.h
@@ -164,7 +164,7 @@
return d->deserialize(ptr, {__VA_ARGS__}); \
} \
bool serialize(Serializer* s, const void* ptr) const override { \
- return s->serialize(ptr, {__VA_ARGS__}); \
+ return s->fields(ptr, {__VA_ARGS__}); \
} \
}; \
static TI typeinfo; \
diff --git a/src/json_serializer.cpp b/src/json_serializer.cpp
index d3db29c..12fdf9f 100644
--- a/src/json_serializer.cpp
+++ b/src/json_serializer.cpp
@@ -224,6 +224,19 @@
return true;
}
+bool Serializer::fields(const void* object,
+ const std::initializer_list<Field>& fields) {
+ *json = nlohmann::json({}, false, nlohmann::json::value_t::object);
+ for (auto const& f : fields) {
+ if (!field(f.name, [&](dap::Serializer* d) {
+ auto ptr = reinterpret_cast<const uint8_t*>(object) + f.offset;
+ return f.type->serialize(d, ptr);
+ }))
+ return false;
+ }
+ return true;
+}
+
bool Serializer::field(const std::string& name,
const std::function<bool(dap::Serializer*)>& cb) {
Serializer s(&(*json)[name]);
diff --git a/src/json_serializer.h b/src/json_serializer.h
index 6c7cc16..41afe1e 100644
--- a/src/json_serializer.h
+++ b/src/json_serializer.h
@@ -98,6 +98,8 @@
bool serialize(const any& v) override;
bool array(size_t count,
const std::function<bool(dap::Serializer*)>&) override;
+ bool fields(const void* object,
+ const std::initializer_list<Field>& fields) override;
bool field(const std::string& name, const FieldSerializer&) override;
void remove() override;
@@ -130,10 +132,6 @@
return dap::Serializer::serialize(v);
}
- inline bool serialize(const void* o, const std::initializer_list<Field>& f) {
- return dap::Serializer::serialize(o, f);
- }
-
inline bool serialize(const char* v) { return dap::Serializer::serialize(v); }
private:
diff --git a/src/json_serializer_test.cpp b/src/json_serializer_test.cpp
index 5072bcc..53cc9a2 100644
--- a/src/json_serializer_test.cpp
+++ b/src/json_serializer_test.cpp
@@ -56,7 +56,9 @@
DAP_FIELD(o2, "o2"),
DAP_FIELD(inner, "inner"));
-TEST(JSONSerializer, Decode) {}
+struct JSONObjectNoFields {};
+
+DAP_STRUCT_TYPEINFO(JSONObjectNoFields, "json-object-no-fields");
} // namespace dap
@@ -91,3 +93,10 @@
ASSERT_EQ(encoded.o2, decoded.o2);
ASSERT_EQ(encoded.inner.i, decoded.inner.i);
}
+
+TEST(JSONSerializer, SerializeObjectNoFields) {
+ dap::JSONObjectNoFields obj;
+ dap::json::Serializer s;
+ ASSERT_TRUE(s.serialize(obj));
+ ASSERT_EQ(s.dump(), "{}");
+}