orjson is faster than Python’s built-in json module because it’s written in Rust and leverages low-level optimizations.

Let’s see it in action. Imagine a Flask application that frequently serializes Python dictionaries to JSON for API responses.

from flask import Flask, jsonify
import json
import orjson
import time

app = Flask(__name__)

# Sample data
data = {
    "user": {
        "id": 123,
        "username": "testuser",
        "email": "test@example.com",
        "roles": ["admin", "editor"],
        "settings": {
            "theme": "dark",
            "notifications": True,
            "timezone": "UTC"
        }
    },
    "items": [
        {"id": 1, "name": "widget", "price": 19.99},
        {"id": 2, "name": "gadget", "price": 29.99}
    ],
    "timestamp": time.time()
}

@app.route('/json')
def json_route():
    return jsonify(data)

@app.route('/orjson')
def orjson_route():
    return orjson.dumps(data)

if __name__ == '__main__':
    # For demonstration, we'll just run a quick benchmark
    # In a real app, you'd use a tool like wrk or locust
    print("Benchmarking json.dumps...")
    start_time = time.time()
    for _ in range(10000):
        json.dumps(data)
    end_time = time.time()
    print(f"json.dumps: {end_time - start_time:.4f} seconds")

    print("\nBenchmarking orjson.dumps...")
    start_time = time.time()
    for _ in range(10000):
        orjson.dumps(data)
    end_time = time.time()
    print(f"orjson.dumps: {end_time - start_time:.4f} seconds")

    # app.run(debug=True) # Uncomment to run the Flask app

When you run this script, you’ll see a significant difference in the benchmark times. orjson will likely complete the 10,000 iterations in a fraction of the time it takes json.dumps. The jsonify function in Flask also has overhead beyond just serialization.

The problem orjson solves is the performance bottleneck that can occur in I/O-bound applications, like web APIs, when serializing large or complex Python objects into JSON. Python’s built-in json module, while functional, is implemented in Python and incurs significant overhead due to object interpretation, type checking, and Python’s Global Interpreter Lock (GIL). This can become a critical performance issue under heavy load.

orjson tackles this by being a Rust library. Rust is a compiled language that allows for very close-to-the-metal performance. orjson’s implementation is highly optimized, using techniques like pre-allocating buffers, avoiding unnecessary object allocations, and performing type checks and conversions in highly efficient, compiled code. It also has specific optimizations for common Python types and structures. Crucially, it bypasses much of the Python interpreter’s overhead.

To integrate orjson into a Flask application, you typically replace calls to flask.jsonify or json.dumps with orjson.dumps. However, orjson.dumps returns bytes, not a string, and it doesn’t automatically set the Content-Type header to application/json. Flask’s Response object can handle bytes directly, and you can set the header manually.

Here’s how you’d typically use it in a Flask route:

from flask import Flask, Response
import orjson

app = Flask(__name__)

@app.route('/api/data')
def api_data():
    some_complex_data = {
        "list": [1, 2, 3, "a", "b", "c"],
        "nested": {"key": "value", "number": 123.45},
        "boolean": True,
        "null_value": None
    }
    # Use orjson.dumps to get bytes
    json_bytes = orjson.dumps(some_complex_data)
    # Return a Flask Response with the bytes and set the Content-Type header
    return Response(json_bytes, mimetype='application/json')

# if __name__ == '__main__':
#     app.run(debug=True)

The core levers you control are the data structures you’re serializing and how frequently you’re doing it. orjson’s speedup is most pronounced with larger, more complex JSON payloads and in high-throughput scenarios.

One crucial detail is orjson’s strictness regarding JSON standards, particularly its handling of None and datetime objects. By default, orjson.dumps will raise a TypeError if it encounters a Python None or a datetime object that it cannot serialize directly to a standard JSON type (like an ISO 8601 string). To handle datetime objects gracefully, you often need to provide a custom default function, similar to Python’s json module, but orjson’s default hook is called less frequently than json’s because it handles many types natively. For example, orjson.dumps(datetime.datetime.now(), default=lambda x: x.isoformat().encode('utf-8')) works. The encode('utf-8') is necessary because orjson expects bytes from the default handler.

The next step is often dealing with deserialization performance, which libraries like simdjson (often used by orjson for parsing) can also accelerate.

Want structured learning?

Take the full Flask course →