diff --git a/docs/mkdocs/docs/features/binary_formats/bson.md b/docs/mkdocs/docs/features/binary_formats/bson.md index 9b7685131..8c1112e84 100644 --- a/docs/mkdocs/docs/features/binary_formats/bson.md +++ b/docs/mkdocs/docs/features/binary_formats/bson.md @@ -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 diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 75a2aa94c..7896b9f18 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -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(N, 'x'); + json const j = + { + { "entry", json::binary(s) } + }; + + CHECK(!j.at("entry").get_binary().has_subtype()); + + std::vector 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