From f06c8fd8e310ad57087142d395ec1ea5523e2c49 Mon Sep 17 00:00:00 2001
From: Julian Becker <becker.julian@gmail.com>
Date: Fri, 14 Sep 2018 22:58:22 +0200
Subject: [PATCH] BSON: serialization of non-objects is not supported

---
 include/nlohmann/detail/exceptions.hpp        |   1 +
 .../nlohmann/detail/input/binary_reader.hpp   |  28 ++++
 .../nlohmann/detail/input/input_adapters.hpp  |   2 +-
 .../nlohmann/detail/output/binary_writer.hpp  |  55 +++++++
 include/nlohmann/json.hpp                     |  48 +++++++
 single_include/nlohmann/json.hpp              | 134 +++++++++++++++++-
 test/src/unit-bson.cpp                        |  70 +++++++++
 7 files changed, 336 insertions(+), 2 deletions(-)
 create mode 100644 test/src/unit-bson.cpp

diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp
index b73d7b1f..274a88c7 100644
--- a/include/nlohmann/detail/exceptions.hpp
+++ b/include/nlohmann/detail/exceptions.hpp
@@ -220,6 +220,7 @@ json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten
 json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers.
 json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive.
 json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. |
+json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) |
 
 @liveexample{The following code shows how a `type_error` exception can be
 caught.,type_error}
diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp
index 05ab36f3..9f684273 100644
--- a/include/nlohmann/detail/input/binary_reader.hpp
+++ b/include/nlohmann/detail/input/binary_reader.hpp
@@ -80,6 +80,10 @@ class binary_reader
                 result = parse_ubjson_internal();
                 break;
 
+            case input_format_t::bson:
+                result = parse_bson_internal();
+                break;
+
             // LCOV_EXCL_START
             default:
                 assert(false);
@@ -120,6 +124,30 @@ class binary_reader
     }
 
   private:
+
+    bool parse_bson_internal()
+    {
+        int docLen = 0;
+        int byte;
+        for (int i = 0; i < 4; ++i)
+        {
+            byte = get();
+            if (JSON_UNLIKELY(current == std::char_traits<char>::eof()))
+            {
+                if (i == 1)
+                {
+                    return sax->boolean(docLen != 0x00);
+                }
+                return false;
+            }
+            docLen |= static_cast<std::int32_t>(byte) << 8 * i;
+        }
+
+        //sax->null();
+        get();
+        return true;
+    }
+
     /*!
     @param[in] get_char  whether a new character should be retrieved from the
                          input (true, default) or whether the last read
diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp
index c2a20ab7..a877984e 100644
--- a/include/nlohmann/detail/input/input_adapters.hpp
+++ b/include/nlohmann/detail/input/input_adapters.hpp
@@ -18,7 +18,7 @@ namespace nlohmann
 namespace detail
 {
 /// the supported input formats
-enum class input_format_t { json, cbor, msgpack, ubjson };
+enum class input_format_t { json, cbor, msgpack, ubjson, bson };
 
 ////////////////////
 // input adapters //
diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp
index 71e5ec81..f58213f5 100644
--- a/include/nlohmann/detail/output/binary_writer.hpp
+++ b/include/nlohmann/detail/output/binary_writer.hpp
@@ -676,6 +676,37 @@ class binary_writer
         }
     }
 
+
+    /*!
+    @param[in] j  JSON value to serialize
+    @pre       j.type() == value_t::object
+    */
+    void write_bson_object(const BasicJsonType& j)
+    {
+        assert(j.type() == value_t::object);
+
+    }
+
+    /*!
+    @param[in] j  JSON value to serialize
+    @pre       j.type() == value_t::object
+    */
+    void write_bson(const BasicJsonType& j)
+    {
+        switch (j.type())
+        {
+            default:
+                JSON_THROW(type_error::create(317, "JSON value cannot be serialized to requested format"));
+                break;
+            case value_t::discarded:
+                break;
+            case value_t::object:
+                write_bson_object(j);
+                break;
+        }
+    }
+
+
   private:
     /*
     @brief write a number to output input
@@ -704,6 +735,30 @@ class binary_writer
         oa->write_characters(vec.data(), sizeof(NumberType));
     }
 
+    /*
+    @brief write a number to output in little endian format
+
+    @param[in] n number of type @a NumberType
+    @tparam NumberType the type of the number
+    */
+    template<typename NumberType>
+    void write_number_little_endian(const NumberType n)
+    {
+        // step 1: write number to array of length NumberType
+        std::array<CharType, sizeof(NumberType)> vec;
+        std::memcpy(vec.data(), &n, sizeof(NumberType));
+
+        // step 2: write array to output (with possible reordering)
+        if (!is_little_endian)
+        {
+            // reverse byte order prior to conversion if necessary
+            std::reverse(vec.begin(), vec.end());
+        }
+
+        oa->write_characters(vec.data(), sizeof(NumberType));
+    }
+
+
     // UBJSON: write number (floating point)
     template<typename NumberType, typename std::enable_if<
                  std::is_floating_point<NumberType>::value, int>::type = 0>
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
index ee78c1c1..8b6a0170 100644
--- a/include/nlohmann/json.hpp
+++ b/include/nlohmann/json.hpp
@@ -6590,6 +6590,26 @@ class basic_json
         binary_writer<char>(o).write_ubjson(j, use_size, use_type);
     }
 
+
+
+    static std::vector<uint8_t> to_bson(const basic_json& j)
+    {
+        std::vector<uint8_t> result;
+        to_bson(j, result);
+        return result;
+    }
+
+    static void to_bson(const basic_json& j, detail::output_adapter<uint8_t> o)
+    {
+        binary_writer<uint8_t>(o).write_bson(j);
+    }
+
+    static void to_bson(const basic_json& j, detail::output_adapter<char> o)
+    {
+        binary_writer<char>(o).write_bson(j);
+    }
+
+
     /*!
     @brief create a JSON value from an input in CBOR format
 
@@ -6897,6 +6917,34 @@ class basic_json
         return res ? result : basic_json(value_t::discarded);
     }
 
+
+
+
+
+    static basic_json from_bson(detail::input_adapter&& i,
+                                const bool strict = true,
+                                const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::bson, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    template<typename A1, typename A2,
+             detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+    static basic_json from_bson(A1 && a1, A2 && a2,
+                                const bool strict = true,
+                                const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::bson, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+
+
     /// @}
 
     //////////////////////////
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 69e4bddc..606a3574 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -819,6 +819,7 @@ json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten
 json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers.
 json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive.
 json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. |
+json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) |
 
 @liveexample{The following code shows how a `type_error` exception can be
 caught.,type_error}
@@ -1882,7 +1883,7 @@ namespace nlohmann
 namespace detail
 {
 /// the supported input formats
-enum class input_format_t { json, cbor, msgpack, ubjson };
+enum class input_format_t { json, cbor, msgpack, ubjson, bson };
 
 ////////////////////
 // input adapters //
@@ -6020,6 +6021,10 @@ class binary_reader
                 result = parse_ubjson_internal();
                 break;
 
+            case input_format_t::bson:
+                result = parse_bson_internal();
+                break;
+
             // LCOV_EXCL_START
             default:
                 assert(false);
@@ -6060,6 +6065,30 @@ class binary_reader
     }
 
   private:
+
+    bool parse_bson_internal()
+    {
+        int docLen = 0;
+        int byte;
+        for (int i = 0; i < 4; ++i)
+        {
+            byte = get();
+            if (JSON_UNLIKELY(current == std::char_traits<char>::eof()))
+            {
+                if (i == 1)
+                {
+                    return sax->boolean(docLen != 0x00);
+                }
+                return false;
+            }
+            docLen |= static_cast<std::int32_t>(byte) << 8 * i;
+        }
+
+        //sax->null();
+        get();
+        return true;
+    }
+
     /*!
     @param[in] get_char  whether a new character should be retrieved from the
                          input (true, default) or whether the last read
@@ -8317,6 +8346,37 @@ class binary_writer
         }
     }
 
+
+    /*!
+    @param[in] j  JSON value to serialize
+    @pre       j.type() == value_t::object
+    */
+    void write_bson_object(const BasicJsonType& j)
+    {
+        assert(j.type() == value_t::object);
+
+    }
+
+    /*!
+    @param[in] j  JSON value to serialize
+    @pre       j.type() == value_t::object
+    */
+    void write_bson(const BasicJsonType& j)
+    {
+        switch (j.type())
+        {
+            default:
+                JSON_THROW(type_error::create(317, "JSON value cannot be serialized to requested format"));
+                break;
+            case value_t::discarded:
+                break;
+            case value_t::object:
+                write_bson_object(j);
+                break;
+        }
+    }
+
+
   private:
     /*
     @brief write a number to output input
@@ -8345,6 +8405,30 @@ class binary_writer
         oa->write_characters(vec.data(), sizeof(NumberType));
     }
 
+    /*
+    @brief write a number to output in little endian format
+
+    @param[in] n number of type @a NumberType
+    @tparam NumberType the type of the number
+    */
+    template<typename NumberType>
+    void write_number_little_endian(const NumberType n)
+    {
+        // step 1: write number to array of length NumberType
+        std::array<CharType, sizeof(NumberType)> vec;
+        std::memcpy(vec.data(), &n, sizeof(NumberType));
+
+        // step 2: write array to output (with possible reordering)
+        if (!is_little_endian)
+        {
+            // reverse byte order prior to conversion if necessary
+            std::reverse(vec.begin(), vec.end());
+        }
+
+        oa->write_characters(vec.data(), sizeof(NumberType));
+    }
+
+
     // UBJSON: write number (floating point)
     template<typename NumberType, typename std::enable_if<
                  std::is_floating_point<NumberType>::value, int>::type = 0>
@@ -17663,6 +17747,26 @@ class basic_json
         binary_writer<char>(o).write_ubjson(j, use_size, use_type);
     }
 
+
+
+    static std::vector<uint8_t> to_bson(const basic_json& j)
+    {
+        std::vector<uint8_t> result;
+        to_bson(j, result);
+        return result;
+    }
+
+    static void to_bson(const basic_json& j, detail::output_adapter<uint8_t> o)
+    {
+        binary_writer<uint8_t>(o).write_bson(j);
+    }
+
+    static void to_bson(const basic_json& j, detail::output_adapter<char> o)
+    {
+        binary_writer<char>(o).write_bson(j);
+    }
+
+
     /*!
     @brief create a JSON value from an input in CBOR format
 
@@ -17970,6 +18074,34 @@ class basic_json
         return res ? result : basic_json(value_t::discarded);
     }
 
+
+
+
+
+    static basic_json from_bson(detail::input_adapter&& i,
+                                const bool strict = true,
+                                const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::bson, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    template<typename A1, typename A2,
+             detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+    static basic_json from_bson(A1 && a1, A2 && a2,
+                                const bool strict = true,
+                                const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::bson, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+
+
     /// @}
 
     //////////////////////////
diff --git a/test/src/unit-bson.cpp b/test/src/unit-bson.cpp
new file mode 100644
index 00000000..4e17f233
--- /dev/null
+++ b/test/src/unit-bson.cpp
@@ -0,0 +1,70 @@
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++ (test suite)
+|  |  |__   |  |  | | | |  version 3.2.0
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+SPDX-License-Identifier: MIT
+Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include "catch.hpp"
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#include <fstream>
+
+TEST_CASE("BSON")
+{
+    SECTION("individual values")
+    {
+        SECTION("discarded")
+        {
+            // discarded values are not serialized
+            json j = json::value_t::discarded;
+            const auto result = json::to_bson(j);
+            CHECK(result.empty());
+        }
+
+        SECTION("null")
+        {
+            json j = nullptr;
+            REQUIRE_THROWS_AS(json::to_bson(j), json::type_error);
+        }
+
+        SECTION("boolean")
+        {
+            SECTION("true")
+            {
+                json j = true;
+                REQUIRE_THROWS_AS(json::to_bson(j), json::type_error);
+            }
+
+            SECTION("false")
+            {
+                json j = false;
+                REQUIRE_THROWS_AS(json::to_bson(j), json::type_error);
+            }
+        }
+    }
+}