diff --git a/docs/mkdocs/docs/features/binary_formats/bson.md b/docs/mkdocs/docs/features/binary_formats/bson.md index f3b8cf18d0..a162672631 100644 --- a/docs/mkdocs/docs/features/binary_formats/bson.md +++ b/docs/mkdocs/docs/features/binary_formats/bson.md @@ -22,8 +22,7 @@ The library uses the following mapping from JSON values types to BSON types: | number_integer | -2147483648..2147483647 | int32 | 0x10 | | number_integer | 2147483648..9223372036854775807 | int64 | 0x12 | | number_unsigned | 0..2147483647 | int32 | 0x10 | -| number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12 | -| number_unsigned | 9223372036854775808..18446744073709551615 | -- | -- | +| number_unsigned | 2147483648..18446744073709551615 | int64 | 0x11 | | number_float | *any value* | double | 0x01 | | string | *any value* | string | 0x02 | | array | *any value* | document | 0x04 | @@ -73,7 +72,7 @@ The library maps BSON record types to JSON value types as follows: | Symbol | 0x0E | *unsupported* | | JavaScript Code | 0x0F | *unsupported* | | int32 | 0x10 | number_integer | -| Timestamp | 0x11 | *unsupported* | +| Timestamp | 0x11 | number_unsigned | | 128-bit decimal float | 0x13 | *unsupported* | | Max Key | 0x7F | *unsupported* | | Min Key | 0xFF | *unsupported* | diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index a0fee9e333..1df26d2820 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -830,7 +830,7 @@ A parsed number could not be stored as without changing it to NaN or INF. ### json.exception.out_of_range.407 -UBJSON and BSON only support integer numbers up to 9223372036854775807. +UBJSON only supports integer numbers up to 9223372036854775807. !!! failure "Example message" diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index a6e100e761..e12d3165a0 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -319,6 +319,12 @@ class binary_reader return get_number(input_format_t::bson, value) && sax->number_integer(value); } + case 0x11: // uint64 + { + std::uint64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_integer(value); + } + case 0x12: // int64 { std::int64_t value{}; diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index f475d57be8..bc508ac4f1 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -1066,7 +1066,7 @@ class binary_writer { return (value <= static_cast((std::numeric_limits::max)())) ? sizeof(std::int32_t) - : sizeof(std::int64_t); + : sizeof(std::uint64_t); } /*! @@ -1080,14 +1080,14 @@ class binary_writer write_bson_entry_header(name, 0x10 /* int32 */); write_number(static_cast(j.m_data.m_value.number_unsigned), true); } - else if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + else if (j.m_data.m_value.number_unsigned <= std::numeric_limits::max()) { - write_bson_entry_header(name, 0x12 /* int64 */); - write_number(static_cast(j.m_data.m_value.number_unsigned), true); + write_bson_entry_header(name, 0x11 /* uint64 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); } else { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j)); + JSON_THROW(out_of_range::create(407, concat("unsigned integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit into uint64"), &j)); } } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a858728c4c..6f74a4a526 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -9465,6 +9465,12 @@ class binary_reader return get_number(input_format_t::bson, value) && sax->number_integer(value); } + case 0x11: // uint64 + { + std::uint64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_integer(value); + } + case 0x12: // int64 { std::int64_t value{}; @@ -16106,7 +16112,7 @@ class binary_writer { return (value <= static_cast((std::numeric_limits::max)())) ? sizeof(std::int32_t) - : sizeof(std::int64_t); + : sizeof(std::uint64_t); } /*! @@ -16120,14 +16126,14 @@ class binary_writer write_bson_entry_header(name, 0x10 /* int32 */); write_number(static_cast(j.m_data.m_value.number_unsigned), true); } - else if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + else if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { - write_bson_entry_header(name, 0x12 /* int64 */); - write_number(static_cast(j.m_data.m_value.number_unsigned), true); + write_bson_entry_header(name, 0x11 /* uint64 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); } else { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j)); + JSON_THROW(out_of_range::create(407, concat("unsigned integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit into uint64"), &j)); } } diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 13216f2f5a..2e4f4189f3 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -331,7 +331,6 @@ TEST_CASE("BSON") SECTION("non-empty object with unsigned integer (64-bit) member") { - // directly encoding uint64 is not supported in bson (only for timestamp values) json const j = { { "entry", std::uint64_t{0x1234567804030201} } @@ -340,7 +339,7 @@ TEST_CASE("BSON") std::vector const expected = { 0x14, 0x00, 0x00, 0x00, // size (little endian) - 0x12, /// entry: int64 + 0x11, /// entry: uint64 'e', 'n', 't', 'r', 'y', '\x00', 0x01, 0x02, 0x03, 0x04, 0x78, 0x56, 0x34, 0x12, 0x00 // end marker @@ -1134,7 +1133,7 @@ TEST_CASE("BSON numerical data") std::vector const expected_bson = { 0x14u, 0x00u, 0x00u, 0x00u, // size (little endian) - 0x12u, /// entry: int64 + 0x11u, /// entry: uint64 'e', 'n', 't', 'r', 'y', '\x00', static_cast((iu >> (8u * 0u)) & 0xffu), static_cast((iu >> (8u * 1u)) & 0xffu), @@ -1184,7 +1183,7 @@ TEST_CASE("BSON numerical data") std::vector const expected_bson = { 0x14u, 0x00u, 0x00u, 0x00u, // size (little endian) - 0x12u, /// entry: int64 + 0x11u, /// entry: uint64 'e', 'n', 't', 'r', 'y', '\x00', static_cast((iu >> (8u * 0u)) & 0xffu), static_cast((iu >> (8u * 1u)) & 0xffu), @@ -1197,12 +1196,15 @@ TEST_CASE("BSON numerical data") 0x00u // end marker }; - CHECK_THROWS_AS(json::to_bson(j), json::out_of_range&); -#if JSON_DIAGNOSTICS - CHECK_THROWS_WITH_STD_STR(json::to_bson(j), "[json.exception.out_of_range.407] (/entry) integer number " + std::to_string(i) + " cannot be represented by BSON as it does not fit int64"); -#else - CHECK_THROWS_WITH_STD_STR(json::to_bson(j), "[json.exception.out_of_range.407] integer number " + std::to_string(i) + " cannot be represented by BSON as it does not fit int64"); -#endif + const auto bson = json::to_bson(j); + CHECK(bson == expected_bson); + + auto j_roundtrip = json::from_bson(bson); + + CHECK(j.at("entry").is_number_unsigned()); + CHECK(j_roundtrip.at("entry").is_number_integer()); + CHECK(j_roundtrip == j); + CHECK(json::from_bson(bson, true, false) == j); } }