mrkeyoor.com_
Tue 22 Sept 18:48 UTC
PyPIUtilsupdated 21 Sept 2026

ujson review

ujson is a C extension that exposes JSON `loads`, `dumps`, `load`, and `dump` functions with an interface close to Python's standard `json` module. It adds switches for escaping HTML characters, escaping forward slashes, byte rejection, indentation, and ASCII output. The project itself supplies the decisive warning: its architecture is hard to change without risking security bugs, so development is maintenance-only and the maintainers recommend migration to orjson. Version 5.13.0 adds Python 3.15 and manylinux2014 wheels, restores GraalPy macOS arm64 wheels, disables the global interpreter lock for free-threaded Python, tightens UTF-8 checks when byte encoding is explicitly enabled, and moves package metadata into `pyproject.toml`.

Verdict

Do not add ujson to a new codebase; its maintainers give a direct security-architecture reason to migrate away. Keep a pinned installation only where measured compatibility work blocks that migration today.

We installed it

Lab card: what happened when we installed ujsonScreenshot of ujson documentation
Install✓ · 0.2s1 package on disk · 1 MB
Importimport ujson in 0.03s · compiled extensions · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does ujson install cleanly?

Yes. In a fresh container with an empty cache, pip install ujson finished in 0.2s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does ujson need to run?

Python >=3.10, and a platform wheel with compiled extensions. In our run import ujson succeeded in 0.03s.

ujson or orjson: which should you use?

orjson: Choose the replacement recommended by ujson's maintainers when bytes output fits your boundary. Do not add ujson to a new codebase; its maintainers give a direct security-architecture reason to migrate away.

When should you not use ujson?

This is a new dependency choice. The maintainers call ujson maintenance-only and explicitly recommend orjson because this C architecture can introduce buffer-overflow vulnerabilities.

API stability4/5The core file and string functions remain close to Python's `json` module, and encoder options such as `ensure_ascii`, `escape_forward_slashes`, `encode_html_chars`, `indent`, `sort_keys`, `allow_nan`, `reject_bytes`, and `default` are covered by project tests. Maintenance-only status limits feature churn. That same status means compatibility gaps are unlikely to receive broad new behavior, so stable syntax should not be mistaken for a growing standards contract.
Docs3/5The README puts the maintenance and buffer-overflow warning before usage, explains the main encoder switches, publishes benchmark inputs and environment details, describes free-threaded safety, and documents native build variables. It is clear about the recommended replacement. Coverage is thin for decoder edge cases, file APIs, standard-library differences, migration tests, typing, wheel support tables, and exact exception behavior, so the test suite becomes the practical reference for less common options.
Maintenance3/5Version 5.13.0 shipped on June 14, 2026, and the repository was pushed on August 13. GitHub reports 4,497 stars, 32 open issues and pull requests, an unarchived repository, and main as the default branch. The release adds new runtime wheels, free-threaded support, packaging cleanup, and stricter UTF-8 validation. Maintenance is active within a deliberately narrow policy: new features are rejected while critical bugs, security fixes, and Python support continue.
Ecosystem3/5PyPIStats counted 7,598,645 downloads in the latest week, and our install was one package with no direct dependencies and a working 0.03-second import. Wheels cover common runtimes, while the API resembles standard `json` enough for many existing integrations. New adoption is hard to justify because the maintainers point users to orjson, the package has compiled-platform constraints, no `py.typed` marker in our check, and behavior still needs payload-level compatibility tests.

Use it if

  • An existing service already depends on ujson's exact string output and a measured migration regression is harder to absorb immediately.
  • Your own production payload benchmark proves it still removes meaningful CPU time while an orjson migration is scheduled.
  • A short compatibility window needs a `json`-like encoder that returns `str`, with pins and regression tests around every stored or signed payload.
Skip it if

Setup reality

We installed ujson 5.13.0 in a clean Python 3.12 Bookworm container. pip completed in 0.2 seconds and left one package using 1 MB. The package declares no direct dependencies and requires Python 3.10 or newer. It ships a compiled .so, did not include py.typed, and had unknown license metadata in our check. import ujson completed in 0.03 seconds, and pip-audit found no known vulnerabilities. PyPI metadata also leaves the license field empty, while GitHub does not return a recognized SPDX license.

A supported wheel avoids local compilation. Version 5.13.0 adds manylinux2014, Python 3.15, and GraalPy macOS arm64 artifacts, but less common platforms can still build from source. The default build bundles double-conversion and strips Linux debug symbols. Distribution packagers may set UJSON_BUILD_DC_INCLUDES and UJSON_BUILD_DC_LIBS to use a system copy, which makes the resulting extension dependent on that library's ABI. UJSON_BUILD_NO_STRIP=1 keeps debug symbols when crash diagnosis matters.

Treat any swap between json, ujson, and orjson as a serialization change. ujson escapes forward slashes by default, emits ASCII escapes by default, accepts NaN and Infinity unless allow_nan=False, and rejects bytes unless reject_bytes=False. That last escape hatch now has tighter UTF-8 validation in 5.13.0, yet decoded bytes still need explicit compatibility tests. Compare Unicode, surrogate handling, integer limits, float output, non-string keys, custom defaults, sorting, and exact separators on real payloads before changing stored JSON or signed messages.

The README says calls are safe across threads because the module has no global state, and 5.13.0 supports free-threaded execution without the global interpreter lock. Mutating an object in one thread while another serializes it is explicitly unsafe. Take an immutable snapshot or coordinate access. More importantly, no current audit finding cancels the project's architectural warning. Pin the package, keep untrusted-input exposure bounded, and plan migration rather than expanding its footprint.

Patterns

Serialize a Python value encode-json-string

import ujson

payload = ujson.dumps({'id': 42, 'active': True})
assert isinstance(payload, str)

Test exact output before replacing `json.dumps` in signatures, snapshots, or persisted data.

Parse a JSON document decode-json-string

record = ujson.loads('{"id":42,"active":true}')
assert record['id'] == 42

Apply input-size and nesting limits outside ujson when parsing untrusted data.

Write JSON to a text file write-json-file

with open('result.json', 'w', encoding='utf-8') as stream:
    ujson.dump({'status': 'ok'}, stream, ensure_ascii=False)

Use text mode with an explicit encoding. Write through a temporary file and rename when partial files are unacceptable.

Load JSON from a file object read-json-file

with open('result.json', encoding='utf-8') as stream:
    result = ujson.load(stream)

`load` expects a file-like object with `read`; passing a path string is not the same operation.

Keep UTF-8 characters readable emit-unicode

text = ujson.dumps({'city': 'München'}, ensure_ascii=False)
assert text == '{"city":"München"}'

The default escapes non-ASCII code points. Ensure the storage or transport is explicitly UTF-8 before disabling that behavior.

Avoid escaped URL slashes keep-url-slashes

text = ujson.dumps(
    {'url': 'https://example.com/a'},
    escape_forward_slashes=False,
)

ujson escapes forward slashes by default. Both forms are JSON, but exact strings differ.

Encode HTML-sensitive characters escape-html-characters

text = ujson.dumps(
    '<script>one&two</script>',
    encode_html_chars=True,
)

This escapes selected characters in the JSON string. It is not an HTML sanitizer or a safe substitute for context-aware output encoding.

Indent diagnostic output pretty-print-json

print(ujson.dumps({'items': [1, 2]}, indent=2, ensure_ascii=False))

Pretty output costs more bytes and should not be used in a hot transport path without a reason.

Produce sorted object keys sort-object-keys

text = ujson.dumps({'b': 2, 'a': 1}, sort_keys=True)
assert text == '{"a":1,"b":2}'

Sorting alone does not create a cross-library canonical JSON format. Number and escaping behavior still need agreement.

Require standard JSON numbers reject-non-finite-numbers

import math

try:
    ujson.dumps({'value': math.nan}, allow_nan=False)
except OverflowError:
    handle_invalid_number()

Without `allow_nan=False`, ujson can emit NaN and Infinity, which strict JSON consumers may reject.

Convert a datetime explicitly encode-custom-type

from datetime import datetime

def encode(value):
    if isinstance(value, datetime):
        return value.isoformat()
    raise TypeError(type(value).__name__)

text = ujson.dumps({'createdAt': datetime.now()}, default=encode)

A default callback should raise for unknown objects so accidental stringification does not hide a schema error.

Gate a move to the standard library compare-migration-output

import json
import ujson

def assert_compatible(value):
    old = ujson.loads(ujson.dumps(value, ensure_ascii=False))
    new = json.loads(json.dumps(value, ensure_ascii=False))
    assert old == new

Round-trip equality is a starting point. Also compare exact serialized bytes wherever ordering, escaping, hashing, or signing matters.

Alternatives

PackageRegistryPick it when
orjsonPyPIChoose the replacement recommended by ujson's maintainers when bytes output fits your boundary.
msgspecPyPIChoose it when fast JSON should include typed structs and runtime decoding checks.
python-rapidjsonPyPIChoose it for RapidJSON-specific number, datetime, UUID, and parse-mode controls.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.