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(), "{}");
+}