From 5ce7d6bdd7dc28d8d9e8f6d3bc91b541217bdb5d Mon Sep 17 00:00:00 2001
From: Julian Becker <becker.julian@gmail.com>
Date: Sat, 15 Sep 2018 13:03:42 +0200
Subject: [PATCH] BSON: support objects with objects as members

---
 .../nlohmann/detail/input/binary_reader.hpp   |  9 +++++-
 .../nlohmann/detail/output/binary_writer.hpp  | 17 ++++++++++-
 single_include/nlohmann/json.hpp              | 26 +++++++++++++++--
 test/src/unit-bson.cpp                        | 29 +++++++++++++++++++
 4 files changed, 77 insertions(+), 4 deletions(-)

diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp
index d135a4c2..7deb788a 100644
--- a/include/nlohmann/detail/input/binary_reader.hpp
+++ b/include/nlohmann/detail/input/binary_reader.hpp
@@ -231,10 +231,17 @@ class binary_reader
                     sax->null();
                 }
                 break;
+                case 0x03: // object
+                {
+                    string_t key;
+                    get_bson_cstr(key);
+                    sax->key(key);
+                    parse_bson_internal();
+                }
+                break;
             }
         }
 
-        get();
         const auto result = sax->end_object();
 
         return result;
diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp
index 770c7dd3..1ec1d794 100644
--- a/include/nlohmann/detail/output/binary_writer.hpp
+++ b/include/nlohmann/detail/output/binary_writer.hpp
@@ -775,6 +775,18 @@ class binary_writer
         }
     }
 
+    std::size_t write_bson_object_internal(const typename BasicJsonType::string_t& name, const BasicJsonType& j)
+    {
+        oa->write_character(static_cast<CharType>(0x03)); // object
+        oa->write_characters(
+            reinterpret_cast<const CharType*>(name.c_str()),
+            name.size() + 1u);
+
+        auto const embedded_document_size = write_bson_object(j);
+
+        return /*id*/ 1ul + name.size() + 1ul + embedded_document_size;
+    }
+
     std::size_t write_bson_object_entry(const typename BasicJsonType::string_t& name, const BasicJsonType& j)
     {
         switch (j.type())
@@ -782,6 +794,8 @@ class binary_writer
             default:
                 JSON_THROW(type_error::create(317, "JSON value cannot be serialized to requested format"));
                 break;
+            case value_t::object:
+                return write_bson_object_internal(name, j);
             case value_t::boolean:
                 return write_bson_boolean(name, j);
             case value_t::number_float:
@@ -803,7 +817,7 @@ class binary_writer
     @param[in] j  JSON value to serialize
     @pre       j.type() == value_t::object
     */
-    void write_bson_object(const BasicJsonType& j)
+    std::size_t write_bson_object(const BasicJsonType& j)
     {
         assert(j.type() == value_t::object);
         auto document_size_offset = oa->reserve_characters(4ul);
@@ -816,6 +830,7 @@ class binary_writer
 
         oa->write_character(static_cast<CharType>(0x00));
         write_number_little_endian_at(document_size_offset, document_size);
+        return document_size;
     }
 
     /*!
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index fa1d8e39..6201f047 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -6215,10 +6215,17 @@ class binary_reader
                     sax->null();
                 }
                 break;
+                case 0x03: // object
+                {
+                    string_t key;
+                    get_bson_cstr(key);
+                    sax->key(key);
+                    parse_bson_internal();
+                }
+                break;
             }
         }
 
-        get();
         const auto result = sax->end_object();
 
         return result;
@@ -8609,6 +8616,18 @@ class binary_writer
         }
     }
 
+    std::size_t write_bson_object_internal(const typename BasicJsonType::string_t& name, const BasicJsonType& j)
+    {
+        oa->write_character(static_cast<CharType>(0x03)); // object
+        oa->write_characters(
+            reinterpret_cast<const CharType*>(name.c_str()),
+            name.size() + 1u);
+
+        auto const embedded_document_size = write_bson_object(j);
+
+        return /*id*/ 1ul + name.size() + 1ul + embedded_document_size;
+    }
+
     std::size_t write_bson_object_entry(const typename BasicJsonType::string_t& name, const BasicJsonType& j)
     {
         switch (j.type())
@@ -8616,6 +8635,8 @@ class binary_writer
             default:
                 JSON_THROW(type_error::create(317, "JSON value cannot be serialized to requested format"));
                 break;
+            case value_t::object:
+                return write_bson_object_internal(name, j);
             case value_t::boolean:
                 return write_bson_boolean(name, j);
             case value_t::number_float:
@@ -8637,7 +8658,7 @@ class binary_writer
     @param[in] j  JSON value to serialize
     @pre       j.type() == value_t::object
     */
-    void write_bson_object(const BasicJsonType& j)
+    std::size_t write_bson_object(const BasicJsonType& j)
     {
         assert(j.type() == value_t::object);
         auto document_size_offset = oa->reserve_characters(4ul);
@@ -8650,6 +8671,7 @@ class binary_writer
 
         oa->write_character(static_cast<CharType>(0x00));
         write_number_little_endian_at(document_size_offset, document_size);
+        return document_size;
     }
 
     /*!
diff --git a/test/src/unit-bson.cpp b/test/src/unit-bson.cpp
index 8de05e80..bdc6fe74 100644
--- a/test/src/unit-bson.cpp
+++ b/test/src/unit-bson.cpp
@@ -351,5 +351,34 @@ TEST_CASE("BSON")
             CHECK(json::from_bson(result) == j);
             CHECK(json::from_bson(result, true, false) == j);
         }
+
+        SECTION("non-empty object with object member")
+        {
+            // directly encoding uint64 is not supported in bson (only for timestamp values)
+            json j =
+            {
+                { "entry", json::object() }
+            };
+
+            std::vector<uint8_t> expected =
+            {
+                0x11, 0x00, 0x00, 0x00, // size (little endian)
+                0x03, /// entry: embedded document
+                'e', 'n', 't', 'r', 'y', '\x00',
+
+                0x05, 0x00, 0x00, 0x00, // size (little endian)
+                // no entries
+                0x00, // end marker (embedded document)
+
+                0x00 // end marker
+            };
+
+            const auto result = json::to_bson(j);
+            CHECK(result == expected);
+
+            // roundtrip
+            CHECK(json::from_bson(result) == j);
+            CHECK(json::from_bson(result, true, false) == j);
+        }
     }
 }