How PyMongo Translates Python Dictionaries to BSON

When working with MongoDB in Python, PyMongo automatically converts native Python dictionary objects into BSON (Binary JSON) byte streams required by the database engine. This article provides a technical overview of how PyMongo performs this serialization process, covering the underlying bson module, data type mapping, recursive document traversal, and the byte-level construction of BSON payloads.

The Underlying Serialization Engine: bson

PyMongo relies on its integrated bson library to handle conversion. For performance, this library is implemented as a C extension (_cbson), falling back to a pure Python implementation only when C extensions cannot be compiled.

The C extension directly interfaces with Python’s C API, allowing it to inspect native Python objects at the memory level and write their binary representations directly into contiguous memory buffers without unnecessary intermediate allocations.

Step-by-Step Translation Process

Serialization occurs whenever an operation writes or queries data (such as insert_one(), update_many(), or find()). The process follows a structured sequence:

1. Allocating the Buffer and Header

A BSON document begins with a 4-byte signed integer representing the total byte length of the document. When PyMongo starts encoding a dictionary:

2. Traversing Key-Value Pairs

PyMongo iterates through the keys and values of the Python dictionary:

3. Handling Nested Structures

When PyMongo encounters nested dictionaries or lists, it processes them recursively:

4. Appending the Null Terminator

After all key-value pairs in a dictionary are appended, PyMongo writes a single null byte (\x00) to signal the end of the document structure.

Python to BSON Type Mapping

PyMongo maps standard Python data types to specific BSON binary representations:

Python Type BSON Type Code Binary Structure
str \x02 (String) 4-byte length + UTF-8 payload + null byte
int (within 32 bits) \x10 (Int32) 4 bytes, signed little-endian
int (within 64 bits) \x12 (Int64) 8 bytes, signed little-endian
float \x01 (Double) 8 bytes, IEEE 754 double precision
bool \x08 (Boolean) 1 byte (\x00 for False, \x01 for True)
None \x0A (Null) No payload bytes (type tag only)
datetime.datetime \x09 (UTC DateTime) 8 bytes, signed int64 milliseconds since Unix epoch
bson.objectid.ObjectId \x07 (ObjectId) 12 raw bytes (timestamp, random value, counter)
bytes \x05 (Binary) 4-byte length + 1-byte subtype + raw byte payload

Performance and Codec Options

Because dictionary key order was historically arbitrary in older Python versions, PyMongo natively handles mapping without requiring explicit ordering. In modern Python (3.7+), insertion order is preserved, and PyMongo writes keys into BSON in the order they appear in the dictionary.

To support non-standard Python types (such as decimal.Decimal or custom classes), PyMongo provides a TypeRegistry and CodecOptions. These allow users to define fallback encoders that convert unsupported Python objects into types that the core C encoder understands prior to serialization.