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
This commit is contained in:
Ben Clayton 2020-01-05 09:25:43 +00:00
parent 3a10d4cabd
commit cdc19ac4d9
5 changed files with 30 additions and 22 deletions

View File

@ -166,6 +166,10 @@ class Serializer {
// Serializer that should be used to encode the n'th array element's data. // 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; 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. // field() encodes a field to the struct object referenced by this Serializer.
// The FieldSerializer will be called with a Serializer used to encode the // The FieldSerializer will be called with a Serializer used to encode the
// field's data. // field's data.
@ -192,10 +196,6 @@ class Serializer {
template <typename T0, typename... Types> template <typename T0, typename... Types>
inline bool serialize(const dap::variant<T0, 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. // deserialize() encodes the given string.
inline bool serialize(const char* v); inline bool serialize(const char* v);
@ -231,18 +231,6 @@ bool Serializer::serialize(const dap::variant<T0, Types...>& var) {
return serialize(var.value); 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) { bool Serializer::serialize(const char* v) {
return serialize(std::string(v)); return serialize(std::string(v));
} }

View File

@ -164,7 +164,7 @@ M member_type(M T::*);
return d->deserialize(ptr, {__VA_ARGS__}); \ return d->deserialize(ptr, {__VA_ARGS__}); \
} \ } \
bool serialize(Serializer* s, const void* ptr) const override { \ bool serialize(Serializer* s, const void* ptr) const override { \
return s->serialize(ptr, {__VA_ARGS__}); \ return s->fields(ptr, {__VA_ARGS__}); \
} \ } \
}; \ }; \
static TI typeinfo; \ static TI typeinfo; \

View File

@ -224,6 +224,19 @@ bool Serializer::array(size_t count,
return true; 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, bool Serializer::field(const std::string& name,
const std::function<bool(dap::Serializer*)>& cb) { const std::function<bool(dap::Serializer*)>& cb) {
Serializer s(&(*json)[name]); Serializer s(&(*json)[name]);

View File

@ -98,6 +98,8 @@ struct Serializer : public dap::Serializer {
bool serialize(const any& v) override; bool serialize(const any& v) override;
bool array(size_t count, bool array(size_t count,
const std::function<bool(dap::Serializer*)>&) override; 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; bool field(const std::string& name, const FieldSerializer&) override;
void remove() override; void remove() override;
@ -130,10 +132,6 @@ struct Serializer : public dap::Serializer {
return dap::Serializer::serialize(v); 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); } inline bool serialize(const char* v) { return dap::Serializer::serialize(v); }
private: private:

View File

@ -56,7 +56,9 @@ DAP_STRUCT_TYPEINFO(JSONTestObject,
DAP_FIELD(o2, "o2"), DAP_FIELD(o2, "o2"),
DAP_FIELD(inner, "inner")); DAP_FIELD(inner, "inner"));
TEST(JSONSerializer, Decode) {} struct JSONObjectNoFields {};
DAP_STRUCT_TYPEINFO(JSONObjectNoFields, "json-object-no-fields");
} // namespace dap } // namespace dap
@ -91,3 +93,10 @@ TEST(JSONSerializer, SerializeDeserialize) {
ASSERT_EQ(encoded.o2, decoded.o2); ASSERT_EQ(encoded.o2, decoded.o2);
ASSERT_EQ(encoded.inner.i, decoded.inner.i); 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(), "{}");
}