mrkeyoor.com_
Thu 06 Aug 00:58 UTC
PyPIUtilsupdated 05 Aug 2026

orjson

orjson is a JSON encoder and decoder for Python written in Rust. It exposes two functions, dumps() and loads(), and that is nearly the whole API. The trade for that small surface is speed and strictness: the README's own benchmarks put dumps() around ten times faster than the standard library and loads() around twice as fast, and it rejects invalid JSON the standard library happily accepts. It also serializes types json cannot touch without help, including dataclasses, datetime, date, time, UUID, and numpy arrays. The one thing that surprises every newcomer is that dumps() returns bytes, not str, because that is what you actually write to a socket or a file.

Verdict

The fastest and strictest JSON library for CPython, and the right default for high-throughput services that already work in bytes. Skip it if you need json's keyword arguments, PyPy, or the ability to report a bug to a maintainer.

API stability5/5Two functions and a set of option flags, unchanged in shape across the 3.x line; the project follows semver and treats serializing a new type without an opt-in flag as a breaking change.
Docs4/5The README is a full reference: every option flag with runnable examples, exact exception semantics, per-type behaviour notes, and benchmark tables. There is no docs site and nothing tutorial-shaped, so newcomers scroll a lot.
Maintenance3/5Releases are steady, 3.11.9 in May 2026 and a push in August 2026, but it is one maintainer and the GitHub issue tracker and pull requests are closed on purpose, so you have no supported way to report a problem.
Ecosystem5/5Around 52M weekly downloads, and it is the encoder that frameworks reach for: FastAPI ships ORJSONResponse, and many libraries accept it as a pluggable serializer.

Use it if

  • You serialize a lot of JSON per second in an API or a queue worker and encoding shows up in your profiles
  • You serialize dataclasses, datetimes, UUIDs, or numpy arrays and are tired of writing a default= hook for each of them
  • You want strict RFC 8259 behaviour: the README shows it rejecting 17 invalid fixtures the standard library accepts, and it refuses NaN and Infinity on input
  • You write bytes anyway, to a socket, a file opened in binary mode, or a Redis value, so the bytes return type costs you nothing
Skip it if

Setup reality

pip install orjson normally pulls a prebuilt wheel and needs no compiler. Wheels cover CPython 3.10 through 3.15 on Linux x86_64, aarch64, armv7, ppc64le and s390x, macOS x86_64 and arm64, and Windows x86, x86_64 and aarch64. Python 3.9 and earlier are out, and PyPy is never supported. Off the wheel path you build from source, which needs Rust 1.95, a C compiler, and maturin, which is a lot for a JSON library in a locked-down CI image. The migration work is the real cost: dumps() gives you bytes so anything expecting str needs .decode(), pretty printing is only OPT_INDENT_2, and every knob you used to pass as a keyword is now a bit flag masked together in option=.

Patterns

Serialize and parsebasic-dumps-loads

import orjson

blob = orjson.dumps({"id": 1, "name": "ada"})
print(blob)              # b'{"id":1,"name":"ada"}'

data = orjson.loads(blob)
print(data["name"])      # ada

dumps returns bytes. If a caller needs str, call .decode() explicitly and accept that you just paid for a copy the library was trying to save you.

Serialize dataclasses with no default hookserialize-dataclass

from dataclasses import dataclass
import orjson

@dataclass
class User:
    id: int
    email: str

orjson.dumps([User(1, "a@example.com"), User(2, "b@example.com")])
# b'[{"id":1,"email":"a@example.com"},{"id":2,"email":"b@example.com"}]'

Works for frozen dataclasses, subclasses, and ones using __slots__, though the README notes __slots__ is the slower path. Field names go out as-is; there is no rename or alias support.

Make naive datetimes serialize as UTCdatetime-naive-utc

import orjson, datetime

value = {"created_at": datetime.datetime(1970, 1, 1)}

orjson.dumps(value)
# b'{"created_at":"1970-01-01T00:00:00"}'   no offset

orjson.dumps(value, option=orjson.OPT_NAIVE_UTC)
# b'{"created_at":"1970-01-01T00:00:00+00:00"}'

Without OPT_NAIVE_UTC a naive datetime is written with no offset, and whatever reads it downstream gets to guess the timezone. Add OPT_UTC_Z if the consumer expects a trailing Z instead of +00:00.

Support a type orjson does not knowdefault-callback

import orjson, decimal

def default(obj):
    if isinstance(obj, decimal.Decimal):
        return str(obj)
    raise TypeError

orjson.dumps({"price": decimal.Decimal("19.99")}, default=default)
# b'{"price":"19.99"}'

Always raise on the unhandled branch. A default that falls off the end returns None implicitly, and that null gets serialized as a legitimate value with no error anywhere.

Know that NaN and Infinity turn into nullnan-becomes-null

import orjson

orjson.dumps([float("nan"), float("inf")])
# b'[null,null]'

orjson.loads(b'[NaN]')
# raises orjson.JSONDecodeError

Neither value is valid JSON, so orjson writes null and refuses to read them back, while the standard library writes and reads NaN. Check for non-finite floats before serializing if silent nulls would corrupt your data.

Pretty-print outputpretty-print

import orjson

print(orjson.dumps(
    {"a": [1, 2]},
    option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS,
).decode())

Two spaces is the only indent width offered. Options are bit flags masked together with |, not keyword arguments, and OPT_SORT_KEYS costs measurable time on large documents.

Serialize dicts with int or UUID keysnon-string-keys

import orjson, uuid

orjson.dumps({1: "a", 2: "b"})
# raises JSONEncodeError: Dict key must be str

orjson.dumps({1: "a"}, option=orjson.OPT_NON_STR_KEYS)
# b'{"1":"a"}'

Off by default so you notice the coercion rather than silently shipping stringified keys. Turning it on also disables the map-key cache, which slows serialization of key-heavy documents.

Serialize numpy arrays directlynumpy-arrays

import orjson, numpy

orjson.dumps(
    {"matrix": numpy.array([[1, 2], [3, 4]])},
    option=orjson.OPT_SERIALIZE_NUMPY,
)
# b'{"matrix":[[1,2],[3,4]]}'

Only contiguous arrays of supported dtypes take the fast path; anything else falls through to default= or raises. numpy scalars such as numpy.float64 also need this flag.

Write line-delimited JSON efficientlywrite-jsonl

import orjson

with open("events.jsonl", "wb") as fh:
    for event in events:
        fh.write(orjson.dumps(event, option=orjson.OPT_APPEND_NEWLINE))

Open the file in binary mode; passing bytes to a text-mode handle raises TypeError. OPT_APPEND_NEWLINE exists because dumps(...) + b'\n' copies the whole immutable buffer for one byte.

Embed already-serialized JSON without re-parsingembed-cached-json

import orjson

cached = redis.get("user:1:profile")   # already valid JSON bytes

orjson.dumps({
    "ok": True,
    "profile": orjson.Fragment(cached),
})

Fragment splices the bytes in verbatim, skipping a loads-then-dumps round trip. It does no validation beyond a UTF-8 check on str input, so garbage in means invalid JSON out.

Use orjson for FastAPI responsesfastapi-response-class

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)

@app.get("/items")
async def items():
    return {"items": [1, 2, 3]}

This only speeds up the encode step. Pydantic model validation and FastAPI's response_model conversion still run first and are usually the larger cost.

Parse bytes directly instead of decoding firstparse-bytes-not-str

import orjson

# slower: builds a throwaway str
data = orjson.loads(response.content.decode("utf-8"))

# faster: bytes, bytearray, and memoryview are accepted as-is
data = orjson.loads(response.content)

The README calls this out explicitly: passing the original buffer keeps both memory use and latency lower. The input must be valid UTF-8 or you get a JSONDecodeError.

Alternatives

PackageRegistryPick it when
msgspecPyPIYou want decoding straight into typed structs with validation, or JSON and MessagePack from one library
ujsonPyPIYou want a faster json that still returns str and keeps most keyword arguments, and you can live with looser correctness
python-rapidjsonPyPIYou need extras orjson refuses, such as configurable number handling, datetime modes, and a JSON Schema validator