Skip to content

Commit

Permalink
Support BSON uint64 de/serialization (#4590)
Browse files Browse the repository at this point in the history
* Support BSON uint64 de/serialization

Signed-off-by: Michael Valladolid <mikevalladolid@gmail.com>

* Treat 0x11 as uint64 and not timestamp specific

Signed-off-by: Michael Valladolid <mikevalladolid@gmail.com>

---------

Signed-off-by: Michael Valladolid <mikevalladolid@gmail.com>
  • Loading branch information
codenut authored Jan 10, 2025
1 parent 1809b3d commit 2d42229
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 18 deletions.
9 changes: 7 additions & 2 deletions docs/mkdocs/docs/features/binary_formats/bson.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The library uses the following mapping from JSON values types to BSON types:
| number_integer | 2147483648..9223372036854775807 | int64 | 0x12 |
| number_unsigned | 0..2147483647 | int32 | 0x10 |
| number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12 |
| number_unsigned | 9223372036854775808..18446744073709551615 | -- | -- |
| number_unsigned | 9223372036854775808..18446744073709551615 | uint64 | 0x11 |
| number_float | *any value* | double | 0x01 |
| string | *any value* | string | 0x02 |
| array | *any value* | document | 0x04 |
Expand Down Expand Up @@ -73,7 +73,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* |
| uint64(Timestamp) | 0x11 | number_unsigned |
| 128-bit decimal float | 0x13 | *unsupported* |
| Max Key | 0x7F | *unsupported* |
| Min Key | 0xFF | *unsupported* |
Expand All @@ -94,3 +94,8 @@ The library maps BSON record types to JSON value types as follows:
```json
--8<-- "examples/from_bson.output"
```

!!! note "Handling of BSON type 0x11"

BSON type 0x11 is used to represent uint64 numbers. This library treats these values purely as uint64 numbers
and does not parse them into date-related formats.
2 changes: 1 addition & 1 deletion docs/mkdocs/docs/home/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,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 support integer numbers up to 9223372036854775807.

!!! failure "Example message"

Expand Down
6 changes: 6 additions & 0 deletions include/nlohmann/detail/input/binary_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,12 @@ class binary_reader
return get_number<std::int64_t, true>(input_format_t::bson, value) && sax->number_integer(value);
}

case 0x11: // uint64
{
std::uint64_t value{};
return get_number<std::uint64_t, true>(input_format_t::bson, value) && sax->number_unsigned(value);
}

default: // anything else not supported (yet)
{
std::array<char, 3> cr{{}};
Expand Down
3 changes: 2 additions & 1 deletion include/nlohmann/detail/output/binary_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,8 @@ class binary_writer
}
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));
write_bson_entry_header(name, 0x11 /* uint64 */);
write_number<std::uint64_t>(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned), true);
}
}

Expand Down
9 changes: 8 additions & 1 deletion single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10006,6 +10006,12 @@ class binary_reader
return get_number<std::int64_t, true>(input_format_t::bson, value) && sax->number_integer(value);
}

case 0x11: // uint64
{
std::uint64_t value{};
return get_number<std::uint64_t, true>(input_format_t::bson, value) && sax->number_unsigned(value);
}

default: // anything else not supported (yet)
{
std::array<char, 3> cr{{}};
Expand Down Expand Up @@ -16733,7 +16739,8 @@ class binary_writer
}
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));
write_bson_entry_header(name, 0x11 /* uint64 */);
write_number<std::uint64_t>(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned), true);
}
}

Expand Down
24 changes: 11 additions & 13 deletions tests/src/unit-bson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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} }
Expand Down Expand Up @@ -531,7 +530,6 @@ TEST_CASE("BSON")

SECTION("Some more complex document")
{
// directly encoding uint64 is not supported in bson (only for timestamp values)
json const j =
{
{"double", 42.5},
Expand Down Expand Up @@ -1164,10 +1162,7 @@ TEST_CASE("BSON numerical data")
std::vector<std::uint64_t> const numbers
{
static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1ULL,
10000000000000000000ULL,
18000000000000000000ULL,
(std::numeric_limits<std::uint64_t>::max)() - 1ULL,
(std::numeric_limits<std::uint64_t>::max)(),
0xffffffffffffffff,
};

for (const auto i : numbers)
Expand All @@ -1184,7 +1179,7 @@ TEST_CASE("BSON numerical data")
std::vector<std::uint8_t> const expected_bson =
{
0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
0x12u, /// entry: int64
0x11u, /// entry: uint64
'e', 'n', 't', 'r', 'y', '\x00',
static_cast<std::uint8_t>((iu >> (8u * 0u)) & 0xffu),
static_cast<std::uint8_t>((iu >> (8u * 1u)) & 0xffu),
Expand All @@ -1197,12 +1192,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_unsigned());
CHECK(j_roundtrip == j);
CHECK(json::from_bson(bson, true, false) == j);
}
}

Expand Down

0 comments on commit 2d42229

Please sign in to comment.