8.0 KiB
BSON
BSON, short for Binary JSON, is a binary-encoded serialization of JSON-like documents. Like JSON, BSON supports the embedding of documents and arrays within other documents and arrays. BSON also contains extensions that allow representation of data types that are not part of the JSON spec. For example, BSON has a Date type and a BinData type.
References
- BSON Website - the main source on BSON
- BSON Specification - the specification
Serialization
The library uses the following mapping from JSON values types to BSON types:
| JSON value type | value/range | BSON type | marker |
|---|---|---|---|
| null | null |
null | 0x0A |
| boolean | true, false |
boolean | 0x08 |
| number_integer | -9223372036854775808..-2147483649 | int64 | 0x12 |
| 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 | uint64 | 0x11 |
| number_float | any value | double | 0x01 |
| string | any value | string | 0x02 |
| array | any value | document | 0x04 |
| object | any value | document | 0x03 |
| binary | any value | binary | 0x05 |
Incomplete mapping
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.
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.
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: serialize a JSON value to BSON
#include <iostream>
#include <iomanip>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using namespace nlohmann::literals;
int main()
{
// create a JSON value
json j = R"({"compact": true, "schema": 0})"_json;
// serialize it to BSON
std::vector<std::uint8_t> v = json::to_bson(j);
// print the vector content
for (auto& byte : v)
{
std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)byte << " ";
}
std::cout << std::endl;
}
Output:
0x1b 0x00 0x00 0x00 0x08 0x63 0x6f 0x6d 0x70 0x61 0x63 0x74 0x00 0x01 0x10 0x73 0x63 0x68 0x65 0x6d 0x61 0x00 0x00 0x00 0x00 0x00 0x00
Deserialization
The library maps BSON record types to JSON value types as follows:
| BSON type | BSON marker byte | JSON value type |
|---|---|---|
| double | 0x01 | number_float |
| string | 0x02 | string |
| document | 0x03 | object |
| array | 0x04 | array |
| binary | 0x05 | binary |
| undefined | 0x06 | unsupported |
| ObjectId | 0x07 | unsupported |
| boolean | 0x08 | boolean |
| UTC Date-Time | 0x09 | unsupported |
| null | 0x0A | null |
| Regular Expr. | 0x0B | unsupported |
| DB Pointer | 0x0C | unsupported |
| JavaScript Code | 0x0D | unsupported |
| Symbol | 0x0E | unsupported |
| JavaScript Code w/ scope | 0x0F | unsupported |
| int32 | 0x10 | number_integer |
| uint64(Timestamp) | 0x11 | number_unsigned |
| int64 | 0x12 | number_integer |
| 128-bit decimal float | 0x13 | unsupported |
| Max Key | 0x7F | unsupported |
| Min Key | 0xFF | unsupported |
Incomplete mapping
The mapping is incomplete. The unsupported mappings are indicated in the table above.
Handling of BSON type 0x11
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.
Lenient BSON input handling
The BSON reader is lenient in a few areas where the BSON specification is more restrictive:
- array element keys are not checked against the required decimal sequence (
0,1,2, ...), - any non-zero byte is accepted as
truefor the boolean type, and - the payload for binary subtype
0x02is returned as-is, including its inner length prefix.
If BSON input must be validated for strict specification compliance, validate it separately before passing it to from_bson().
Ill-formed UTF-8 in string values
The BSON specification requires string values (type 0x02) to be valid UTF-8, but this is not required of a decoder, so checking is opt-in: with the error_handler parameter left at keep (the default), from_bson() accepts a string value whose bytes are not valid UTF-8 and hands them back unchanged. Passing error_handler_t::strict makes from_bson() check and throw parse_error.113 for ill-formed UTF-8, and replace/ignore sanitize the string instead of keeping it. However, dump() still requires valid UTF-8 and throws type_error.316 for a value read with the default keep handler, unless an error handler is passed that replaces or ignores the ill-formed bytes. to_bson()'s own error_handler parameter defaults to keep, so such a string value or element (key) name is written unchanged; with strict (the default if JSON_STRICT_BINARY_UTF8 is enabled), it throws the same exception instead. Element (key) names are never validated on read, since they are read byte-by-byte as a C string. binary values (type 0x05) are unaffected, since they are not required to hold text.
Example: deserialize a JSON value from BSON
#include <iostream>
#include <iomanip>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// create byte vector
std::vector<std::uint8_t> v = {0x1b, 0x00, 0x00, 0x00, 0x08, 0x63, 0x6f, 0x6d,
0x70, 0x61, 0x63, 0x74, 0x00, 0x01, 0x10, 0x73,
0x63, 0x68, 0x65, 0x6d, 0x61, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00
};
// deserialize it with BSON
json j = json::from_bson(v);
// print the deserialized JSON value
std::cout << std::setw(2) << j << std::endl;
}
Output:
{
"compact": true,
"schema": 0
}