JSON to MongoDB

MongoDB stores data as BSON documents — a binary superset of JSON. Any valid JSON object maps directly to a MongoDB document. You can insert JSON into MongoDB via the mongoimport CLI tool, the MongoDB shell, Node.js with the native driver or Mongoose, or Python with pymongo. This guide covers all four approaches, explains how JSON differs from BSON, and shows how to handle special types like dates and ObjectIds that JSON cannot express natively.

Key concept: JSON and BSON

MongoDB's native storage format is BSON (Binary JSON), which extends JSON with additional types that plain JSON cannot represent: Date, ObjectId, Binary, Decimal128, and Int32/Int64. When you insert a JavaScript object or Python dict, the MongoDB driver automatically serializes it to BSON before writing to disk. When you query, the driver deserializes BSON back into the language's native object. The conversion is transparent — you work with JSON-like objects in your code and MongoDB handles the rest. For a deep comparison of the two formats, see JSON vs BSON.

Every MongoDB document must have an _id field that serves as the primary key. If you insert a document without one, the driver automatically adds an ObjectId — a 12-byte identifier that encodes a timestamp, machine ID, and sequence counter. ObjectIds are not valid JSON (they are BSON-specific), but drivers serialize them to a string representation like "507f1f77bcf86cd799439011" when returning data as JSON. You can also provide your own _id — any value that is unique within the collection works, including a string, integer, or UUID.

One practical consequence: if you export a MongoDB collection to JSON (via mongoexport) and then re-import it, BSON-specific types are encoded in Extended JSON format — for example, {"$date": "2026-05-11T00:00:00Z"} for a Date, and {"$oid": "507f1f77bcf86cd799439011"} for an ObjectId. The mongoimport tool understands Extended JSON natively, but plain JSON.parse() will not — keep this in mind when moving data between MongoDB and other systems.

mongoimport — import a JSON file from the CLI

mongoimport is the fastest way to load a JSON file into MongoDB. It ships with the MongoDB Database Tools package (separate from mongod since MongoDB 4.4) and supports two file formats: a JSON array (one document per element) and NDJSON (one JSON document per line, also called newline-delimited JSON). Use --jsonArray for array files; omit it for NDJSON.

# Import a JSON array from file
mongoimport --uri "mongodb://localhost:27017" \
  --db mydb \
  --collection users \
  --file users.json \
  --jsonArray

# Import NDJSON (one document per line, no --jsonArray flag)
mongoimport --uri "mongodb://localhost:27017" \
  --db mydb \
  --collection users \
  --file users.ndjson

# With MongoDB Atlas (cloud)
mongoimport --uri "mongodb+srv://user:pass@cluster.mongodb.net/mydb" \
  --collection users \
  --file users.json \
  --jsonArray

The --jsonArray flag tells mongoimport to treat the entire file as a single JSON array — each element in the array becomes one document. Without the flag, mongoimport expects NDJSON: each line must be a complete, valid JSON object. NDJSON is preferable for very large files because the tool can stream one line at a time without loading the entire file into memory. For files with millions of records, consider splitting into multiple NDJSON files and importing in parallel. You can learn more about the NDJSON format in JSON vs NDJSON.

Two additional flags are commonly useful: --upsertFields performs an upsert instead of an insert (matching on the specified fields), and --drop drops the collection before importing — useful for full refreshes. Add --numInsertionWorkers N (default: 1) to parallelize writes for large imports on multi-core machines.

Node.js — MongoDB native driver

The mongodb npm package is the official low-level driver. Install it with npm install mongodb. Use insertOne for a single document and insertMany for a JSON array. Both methods accept plain JavaScript objects — the driver handles BSON serialization automatically.

const { MongoClient } = require('mongodb')

const uri = 'mongodb://localhost:27017'
const client = new MongoClient(uri)

async function insertJson() {
  await client.connect()
  const db = client.db('mydb')
  const collection = db.collection('users')

  // Insert a single JSON object
  const user = { name: 'Alice', email: 'alice@example.com', age: 30 }
  const result = await collection.insertOne(user)
  console.log('Inserted id:', result.insertedId)

  // Insert a JSON array
  const users = [
    { name: 'Bob', email: 'bob@example.com', age: 25 },
    { name: 'Carol', email: 'carol@example.com', age: 35 },
  ]
  const bulkResult = await collection.insertMany(users)
  console.log('Inserted:', bulkResult.insertedCount, 'documents')

  await client.close()
}

insertJson()

insertOne returns an InsertOneResult whose insertedId is the _id of the new document (an ObjectId if you didn't provide one). insertMany returns an InsertManyResult with insertedCount and an insertedIds map from array index to _id. By default, insertMany is ordered — it stops on the first write error. Pass { ordered: false } as the second argument to continue past errors (useful when loading bulk data that may contain occasional duplicates on a unique index).

Node.js — Mongoose (schema-based)

Mongoose wraps the native driver with schema definitions, built-in validators, lifecycle middleware (pre/post hooks), virtual properties, and populated document references. Install with npm install mongoose.

const mongoose = require('mongoose')

const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  age: Number,
  createdAt: { type: Date, default: Date.now },
})

const User = mongoose.model('User', userSchema)

async function insertUsers() {
  await mongoose.connect('mongodb://localhost:27017/mydb')

  // Insert from a JSON object
  const user = await User.create({ name: 'Alice', email: 'alice@example.com', age: 30 })
  console.log('Created:', user._id)

  // Bulk insert from a JSON array
  const users = require('./users.json')
  await User.insertMany(users, { ordered: false }) // ordered:false continues on error

  await mongoose.disconnect()
}

When to use Mongoose vs the native driver: Use Mongoose when you need runtime schema validation (e.g., required fields, type coercion, custom validators), middleware that fires on save or delete (e.g., hashing a password before insert), or populate() to resolve references between collections. Use the native driver directly when you prioritize throughput and flexibility — it has lower overhead, works with any document shape, and is better suited for bulk data pipelines or microservices where schema enforcement is handled at a different layer. Note that User.insertMany() in Mongoose bypasses individual document middleware (pre-save hooks) for performance; use User.create() in a loop if you need hooks to fire on every document.

Python — pymongo

pymongo is the official Python driver for MongoDB. Install it with pip install pymongo. The API mirrors the Node.js driver: insert_one for a single document, insert_many for a list. Python dicts map directly to MongoDB documents.

from pymongo import MongoClient
import json

client = MongoClient('mongodb://localhost:27017')
db = client['mydb']
collection = db['users']

# Insert a single document
user = {'name': 'Alice', 'email': 'alice@example.com', 'age': 30}
result = collection.insert_one(user)
print('Inserted id:', result.inserted_id)

# Load and insert a JSON file
with open('users.json', 'r') as f:
    users = json.load(f)

if isinstance(users, list):
    result = collection.insert_many(users)
    print('Inserted:', len(result.inserted_ids), 'documents')
else:
    result = collection.insert_one(users)
    print('Inserted id:', result.inserted_id)

client.close()

insert_one mutates the dict in place — it adds an _id key to your original dict after the insert. This is a common gotcha: if you reuse the same dict in a loop without removing _id, subsequent inserts will fail with a duplicate key error. Either deep-copy each document before inserting, or use insert_many for batch operations. For MongoDB Atlas, replace the URI with your Atlas connection string: MongoClient('mongodb+srv://user:pass@cluster.mongodb.net/').

Handle dates and special BSON types

JSON has no Date type — dates are stored as ISO 8601 strings like "2026-05-11T00:00:00.000Z". If you insert them as strings, MongoDB stores them as strings and you lose the ability to use date operators ($gt, $lt, $dateToString) and TTL indexes. You have two strategies:

Strategy 1: Store as ISO string (simpler, lossy)

Store dates as-is and convert at query time. This works for display purposes but date range queries require lexicographic string comparison, which only works correctly if all dates are in ISO 8601 UTC format (they sort identically to chronological order).

// Stored as a plain string — works for display, limited for queries
{"createdAt": "2026-05-11T00:00:00.000Z"}

Strategy 2: Convert to BSON Date before inserting (recommended)

// Node.js: convert ISO strings to Date objects before inserting
const doc = JSON.parse(jsonString)
doc.createdAt = new Date(doc.createdAt)
await collection.insertOne(doc)

// For a batch: convert all date fields in an array
const docs = JSON.parse(jsonArrayString)
const dateFields = ['createdAt', 'updatedAt', 'deletedAt']
const converted = docs.map(doc => {
  const out = { ...doc }
  for (const field of dateFields) {
    if (out[field]) out[field] = new Date(out[field])
  }
  return out
})
await collection.insertMany(converted)
# Python: convert strings to datetime before inserting
from datetime import datetime, timezone

def parse_iso(s):
    # Replace Z with +00:00 for Python < 3.11 compatibility
    return datetime.fromisoformat(s.replace('Z', '+00:00'))

doc['created_at'] = parse_iso(doc['created_at'])
collection.insert_one(doc)

# For a batch
date_fields = ['created_at', 'updated_at']
for document in documents:
    for field in date_fields:
        if field in document and isinstance(document[field], str):
            document[field] = parse_iso(document[field])
collection.insert_many(documents)

Storing dates as BSON Date objects enables MongoDB's full suite of date operators. For example, you can query documents created in the last 7 days: { createdAt: { $gte: new Date(Date.now() - 7 * 86400000) } }. You can also create a TTL index to automatically expire documents: db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }). Neither is possible if the field is stored as a string.

Validate your JSON before inserting into MongoDB — catch syntax errors early.

Open JSON Formatter

Frequently asked questions

How do I insert a JSON file into MongoDB?

The fastest method is mongoimport. For a JSON array file: mongoimport --uri "mongodb://localhost:27017" --db mydb --collection users --file data.json --jsonArray. For NDJSON (one document per line): omit --jsonArray. For programmatic insertion, use the MongoDB native driver (insertOne / insertMany) or pymongo in Python. Mongoose adds schema validation on top of the native driver.

Does MongoDB store JSON or BSON?

MongoDB stores data as BSON (Binary JSON) internally, not as text JSON. BSON adds types that JSON lacks: Date, ObjectId, Int32, Int64, Decimal128, and Binary. When you insert a JavaScript object or Python dict, the driver automatically converts it to BSON. When you query, it's converted back. The _id field is automatically added as an ObjectId if not provided. JSON is the interchange format; BSON is the storage format. See JSON vs BSON for a full comparison.

What is the difference between insertOne and insertMany in MongoDB?

insertOne inserts a single document and returns an InsertOneResult with the new document's _id. insertMany accepts an array and returns an InsertManyResult with all inserted IDs. By default, insertMany uses ordered: true — it stops on the first error. Set ordered: false to continue inserting remaining documents even when one fails (useful for bulk loads with occasional duplicates).

How do I handle duplicate key errors when inserting JSON into MongoDB?

Create a unique index on the field that must be unique: db.collection.createIndex({ email: 1 }, { unique: true }). Then handle E11000 duplicate key error in your code. In Node.js: catch the error and check err.code === 11000. In pymongo: catch DuplicateKeyError from pymongo.errors. For bulk inserts, use ordered: false to skip duplicates and continue.

How do I convert JSON dates to MongoDB Date objects?

JSON has no native Date type — dates appear as strings like "2026-05-11T00:00:00.000Z". To store them as proper BSON Dates (enabling date range queries), convert them before inserting. In Node.js: doc.createdAt = new Date(doc.createdAt). In Python: doc['created_at'] = datetime.fromisoformat(doc['created_at'].replace('Z', '+00:00')). Storing as BSON Date enables MongoDB date operators: $gt, $lt, $dateToString, and TTL indexes.

What is the difference between Mongoose and the MongoDB native driver?

The MongoDB native driver (mongodb npm package) is the low-level client — fast, flexible, no schema. Mongoose wraps it with schema definitions, validators, middleware (pre/post hooks), virtuals, and populated document references. Use the native driver for high-throughput bulk operations, flexible document structures, or when you want minimal overhead. Use Mongoose when you need runtime schema validation, relationships between collections, or the ActiveRecord-style model API in a Node.js application.

Ready to insert your JSON?

Use Jsonic to format and validate your JSON before inserting into MongoDB — catch syntax errors before they become write errors.

Open JSON Formatter