Document BSON interoperability and subtype-less binary round trips (#5330)

- warn about BSON marker 0x11 interoperability in both directions
- explain subtype-less binary normalization to subtype 0x00
- add a round-trip test for binary values without a subtype

Signed-off-by: YingqiDuan <141370165+YingqiDuan@users.noreply.github.com>
This commit is contained in:
YingqiDuan
2026-07-31 08:33:07 +02:00
committed by GitHub
parent dd24e2dffd
commit de8a099ba5
2 changed files with 50 additions and 2 deletions
@@ -35,6 +35,19 @@ The library uses the following mapping from JSON values types to BSON types:
The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON.
Also, keys may not contain U+0000, since they are serialized a zero-terminated c-strings.
!!! warning "BSON type 0x11 interoperability"
The BSON specification defines type `0x11` as a Timestamp. This library uses marker `0x11` when serializing
`number_unsigned` values in the range `9223372036854775808..18446744073709551615`. Other BSON implementations may
therefore interpret these values as Timestamps instead of unsigned integers.
!!! info "Binary values without a subtype"
BSON requires every binary value to have a subtype. If a binary value has no subtype, this library serializes it
with the generic subtype `0x00`. After deserialization, `has_subtype()` returns `true` and `subtype()` returns `0`.
As a result, serializing and deserializing a JSON object containing such a value produces a different JSON object,
even though the binary data is unchanged.
??? example
```cpp
@@ -82,8 +95,8 @@ The library maps BSON record types to JSON value types as follows:
!!! 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.
This library deserializes BSON type `0x11` (Timestamp) as a `number_unsigned` value. The 64-bit value is preserved,
but the Timestamp type information is not.
??? example
+35
View File
@@ -529,6 +529,41 @@ TEST_CASE("BSON")
CHECK(json::from_bson(result, true, false) == j);
}
SECTION("non-empty object with binary member without subtype")
{
const size_t N = 10;
const auto s = std::vector<std::uint8_t>(N, 'x');
json const j =
{
{ "entry", json::binary(s) }
};
CHECK(!j.at("entry").get_binary().has_subtype());
std::vector<std::uint8_t> const expected =
{
0x1B, 0x00, 0x00, 0x00, // size (little endian)
0x05, // entry: binary
'e', 'n', 't', 'r', 'y', '\x00',
0x0A, 0x00, 0x00, 0x00, // size of binary (little endian)
0x00, // Generic binary subtype
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x00 // end marker
};
const auto result = json::to_bson(j);
CHECK(result == expected);
// roundtrip adds the generic binary subtype
const auto roundtrip = json::from_bson(result);
CHECK(roundtrip != j);
CHECK(roundtrip.at("entry").get_binary().has_subtype());
CHECK(roundtrip.at("entry").get_binary().subtype() == 0);
CHECK(json::from_bson(result, true, false) == roundtrip);
}
SECTION("non-empty object with binary member with subtype")
{
// an MD5 hash