Data Types & BSON

MongoDB Fundamentals — Data Types & BSON
MongoDB Fundamentals
Chapter 6 · Data Types & BSON

🔢 Data Types & BSON

Chapter 1 introduced BSON briefly. This chapter goes deeper into the actual types it supports, and covers $jsonSchema validation — MongoDB's opt-in answer to Chapter 5's point that schema flexibility is a deliberate trade-off, not a lack of options.

BSON Types in Depth

TypeNotes
ObjectIdMongoDB's default unique identifier type
StringUTF-8 text
Int32 / Int64Whole numbers
DoubleStandard floating-point — the default for decimal literals
Decimal128Exact decimal representation — see this chapter's money warning below
Booleantrue / false
DateA real timestamp type — see the string-vs-Date gotcha below
ArrayAn ordered list, can hold any mix of types, including nested arrays/objects
ObjectA nested (embedded) document
NullAn explicit absence of a value

ObjectId: MongoDB's Default Unique Identifier

An ObjectId is a 12-byte value made of a 4-byte creation timestamp, a 5-byte random value, and a 3-byte incrementing counter. One useful consequence: ObjectIds are roughly sortable by creation time, and the creation timestamp can be pulled directly out of an ObjectId with no separate stored field needed:

Extracting a Timestamp from an ObjectId

const id = ObjectId("64f1a2b3c4d5e6f7a8b9c0d1"); id.getTimestamp() // ISODate("2023-09-01T14:22:11.000Z") — no createdAt field required

Dates: A Common Gotcha

MongoDB has a real Date BSON type (stored internally as milliseconds since the Unix epoch). A common mistake is storing a date as a plain string instead — "2026-07-06" — rather than an actual Date value.

Date-as-String vs. a Real Date

Stored as a String

{ createdAt: "2026-07-06" }
Range queries ($gt/$lt) compare strings lexicographically, character by character — which happens to work for consistently-formatted YYYY-MM-DD strings, but breaks the moment formats mix or times are involved.

Stored as a Real Date

{ createdAt: ISODate("2026-07-06") }
Range queries compare actual points in time correctly, regardless of formatting, time zones, or precision.

Arrays and Nested Documents

Arrays can hold any mix of types, including further nested arrays or objects — Chapter 5's embedding examples (addresses, comments) are really just arrays of nested documents. Nesting can go arbitrarily deep, but very deep nesting tends to hurt both query readability and performance — a practical reason to keep documents reasonably flat where possible, on top of Chapter 5's growth-based reasons for choosing embedding vs. referencing in the first place.

Decimal128: Precise Numbers for Money

The default numeric type for a decimal literal in mongosh is Double — ordinary binary floating-point, the same representation used in most programming languages, which cannot represent every decimal value exactly. Decimal128 stores an exact decimal value instead, avoiding the rounding errors floating-point math can introduce — worth using deliberately for anything involving money.

Schema Validation with $jsonSchema

MongoDB's opt-in mechanism for enforcing structure on a collection — required fields, expected types, and value constraints — directly addressing the "nothing stops inconsistent documents" trade-off from Chapter 1 and Chapter 5:

A Validator Requiring Structure

db.createCollection("users", { validator: { $jsonSchema: { bsonType: "object", required: ["email", "age"], properties: { email: { bsonType: "string" }, age: { bsonType: "int", minimum: 0 } } } } }) // inserts violating this shape are now rejected, restoring the enforcement MySQL provides by default

💻 Coding Challenges

Challenge 1: Pick the Right BSON Type

Recommend a BSON type for each: (a) a customer's account balance in dollars, (b) an order's placement timestamp, (c) a product's list of tags.

Goal: Practice matching a real field to the type that avoids known pitfalls (money, dates) covered in this chapter.

→ Solution

Challenge 2: Spot the Date-as-String Bug

A query db.orders.find({ placedAt: { $gt: "2026-02-01" } }) is meant to find orders placed after February 1st, but returns some orders from January and misses some from March. Explain the likely root cause.

Goal: Practice diagnosing the string-vs-Date comparison problem in a realistic symptom.

→ Solution

Challenge 3: Write a $jsonSchema Validator

Write a $jsonSchema validator for a products collection requiring a name (string) and a price (number, minimum 0).

Goal: Practice the validator syntax for required fields and value constraints.

→ Solution

⚠️ Gotcha: Money Stored as Double Can Silently Lose Precision

Typing a decimal number in mongosh{ price: 19.99 } — stores it as a Double by default, ordinary binary floating-point. Floating-point can't represent every decimal value exactly, the same well-known issue behind 0.1 + 0.2 not exactly equaling 0.3 in most programming languages. For a display price this rarely matters, but for anything involving repeated financial arithmetic — running totals, tax calculations, currency conversion — those tiny errors can accumulate into a genuinely wrong result. Use NumberDecimal("19.99") to store an exact Decimal128 value instead whenever a field represents real money.

🎯 What's Next

The next chapter covers Indexes — single-field and compound indexes, how MongoDB actually chooses which index to use via explain(), and what a missing index really costs.