Speed Up Django JSON Responses With orjson

Use orjson to make Django and Django REST Framework JSON serialization faster: an orjson renderer and parser for DRF, an ORJSONResponse for plain views, handling of datetimes, Decimals, and UUIDs, and when JSON encoding is actually your bottleneck.

Problem statement

Profiling a Django API endpoint that returns large lists shows a noticeable share of time in json.dumps, after database and serializer work. Python's standard json module is written partly in C but still slow for large payloads with many nested objects, datetimes, and Decimals that need custom handling. orjson is a fast JSON library written in Rust that serializes several times faster and natively handles datetimes, UUIDs, and dataclasses.

JSON in a Django APIQuerydatabase rowsSerializeDRF serializersEncode JSONjson or orjsonResponsebytes to clientorjson speeds up the last encoding step; serializers and queries often matter more.
  1. Measure first: confirm JSON encoding is a meaningful share of response time with a profiler.
  2. pip install orjson and add a DRF renderer that calls orjson.dumps (or use the drf-orjson-renderer package).
  3. For plain Django views, return HttpResponse(orjson.dumps(data), content_type="application/json").
  4. Handle Decimal with an option or default function, because orjson does not serialize Decimal by default.
  5. Optimize the bigger costs too: DRF serializer overhead, N+1 queries, and pagination.

Step-by-step solution

1. Measure where time goes

Use py-spy, a profiling middleware, or your APM's breakdown for a slow endpoint. If most time is in SQL or serializer to_representation, orjson will barely help. It shines when responses are large (hundreds of KB) and already-built Python structures are dumped.

2. Add an orjson renderer for DRF

# api/renderers.py
import orjson
from decimal import Decimal
from rest_framework.renderers import BaseRenderer

def _default(obj):
    if isinstance(obj, Decimal):
        return str(obj)
    raise TypeError

class ORJSONRenderer(BaseRenderer):
    media_type = "application/json"
    format = "json"
    charset = None

    def render(self, data, accepted_media_type=None, renderer_context=None):
        if data is None:
            return b""
        return orjson.dumps(data, default=_default, option=orjson.OPT_NON_STR_KEYS)
REST_FRAMEWORK = {
    "DEFAULT_RENDERER_CLASSES": ["api.renderers.ORJSONRenderer"],
    "DEFAULT_PARSER_CLASSES": ["rest_framework.parsers.JSONParser"],
}

Keep the browsable API renderer in development if you use it. drf-orjson-renderer provides a maintained renderer and parser.

encoding 2 MB of nested data (illustrative)json + DjangoJSONEncoder95 msorjson14 ms0 ms60 ms120 msorjson cuts encoding time sharply for large payloadsdjango-deployment.com
Small responses gain little; measure with your real payloads.

3. Use orjson in plain Django views

import orjson
from django.http import HttpResponse

def stats(request):
    data = build_stats()           # dicts, lists, datetimes, UUIDs
    return HttpResponse(orjson.dumps(data, default=_default), content_type="application/json")

orjson.dumps returns bytes, which HttpResponse accepts directly. JsonResponse uses the standard library encoder; a small subclass can swap in orjson for consistency.

4. Handle types correctly

orjson natively serializes datetime, date, time, UUID, dataclasses, and enums. Differences to watch:

  • Decimal is not supported by default; convert to string (keeps precision) or float in default.
  • Timezone-aware datetimes are output in RFC 3339; naive datetimes have no offset. Keep USE_TZ = True.
  • Non-string dictionary keys need OPT_NON_STR_KEYS.
  • Output is compact (no spaces), which also shrinks payloads.

Compare a few responses before and after to make sure clients see identical data.

5. Fix the bigger costs too

For DRF endpoints, ModelSerializer instantiation per object is often slower than encoding. For read-heavy list endpoints, consider values() with a lightweight serializer, select_related/prefetch_related to remove N+1 queries, and pagination to keep payloads small. Conditional requests (ETags) skip encoding entirely for unchanged data.

cost layers, top to bottomJSON encodingorjson helps hereDRF serializersvalues() or slim serializersPython object creationonly() and paginationSQL queriesindexes, select_relatedFix from the bottom up; encoding is usually the smallest layerdjango-deployment.com
A profiler tells you which layer dominates for each endpoint.

Explanation

The standard json module handles basic types in C but calls back into Python for anything else, such as Django's DjangoJSONEncoder for datetimes and Decimals. orjson implements serialization in Rust with native support for common types, avoiding most Python-level work and producing bytes directly. For large responses, that turns encoding from tens of milliseconds into a few. The improvement is real but bounded: encoding is the last step after queries and serializer logic, which often dominate.

Edge cases / notes

  • Wheels. orjson ships wheels for common platforms; exotic architectures may need a Rust toolchain to build.
  • Streaming. For very large exports, stream JSON Lines or CSV rather than building one giant response.
  • Parsing. orjson parsing is fast too, which helps endpoints receiving large JSON bodies.

Next steps

FAQ

Is orjson a drop-in replacement for json in Django?

Mostly, but it returns bytes and does not serialize Decimal by default. Wrap it in a renderer or helper.

How much faster is orjson?

Several times faster for large payloads in typical benchmarks. Measure with your own responses.

Can DRF use orjson?

Yes, through a custom renderer or the drf-orjson-renderer package.

Will orjson fix a slow API?

Only if encoding is the bottleneck. Queries and serializers usually cost more.

Keep going