ujson in maintenance-only mode: what the C encoder still does for you
Ultra fast JSON decoder and encoder written in C with Python bindings
At a glance
- What is it?
- UltraJSON is a C-backed JSON encoder and decoder for CPython, PyPy and GraalPy. Its own README now tells users to migrate to orjson, so the interesting question is where ujson still earns a place.
- Who is it for?
- Adopt ujson only where you already depend on its exact output conventions, such as escape_forward_slashes=True or ensure_ascii=True, and where a C extension is acceptable. Do not adopt it for new code that can take a dependency on orjson, and do not expect feature work: the README states that all changes other than new Python version support, critical bugs and security fixes will be rejected.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 27 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem ujson solves, and the one it no longer tries to solve
The standard library json module is pure Python. For services that parse or emit large payloads, that shows up as CPU time in the request path. UltraJSON replaces the encoder and decoder with C sources compiled into a CPython extension, so the per-character work happens outside the interpreter loop. The README describes it as "an ultra fast JSON encoder and decoder written in pure C with bindings for Python", and the usage section presents it as a drop-in replacement for most other JSON parsers.
The audience is narrow and specific: Python services where JSON serialization is a measurable share of CPU, and where the calling code already goes through dumps and loads. It is not a schema validator, not a streaming parser, and not a tool for very large documents that never fit in memory. The README's own benchmark table shows the gap it was built to close, and also shows where that gap no longer exists.
The project status warning is the part a reader should weigh first. It states that UltraJSON's architecture is "fundamentally ill-suited to making changes without risk of introducing new security vulnerabilities", that the library is in maintenance-only mode, and that new Python versions, critical bugs and security issues will still be handled while all other changes are rejected. That is an unusually direct statement from a maintainer, and it reframes the adoption question from "is it fast" to "is the speed still worth the frozen surface".
How the C extension is put together, from setup.py and the source tree
The build is a single extension module named ujson. According to setup.py, its sources are the double-conversion C++ files globbed from ./src/ujson/deps/double-conversion/double-conversion/*.cc, plus ./src/ujson/dconv_wrapper.cc, ./src/ujson/ujson.c, ./src/ujson/encode.c and ./src/ujson/decode.c. Include directories are ./src/ujson plus the double-conversion path, and the link line adds -lstdc++ and -lm.
So there are three layers. The Python-facing module lives in ujson.c. Encoding and decoding each have their own C file, which is where the option handling described in the README lives. Number formatting and parsing are delegated to a vendored copy of double-conversion, a C++ library, which is why a C project links against the C++ standard library. The version string is injected at build time through the UJSON_VERSION define, and the version itself comes from setuptools-scm rather than a literal in the source.
Two environment variables in setup.py matter to anyone building from source. UJSON_BUILD_DC_INCLUDES overrides the include path list for double-conversion, and UJSON_BUILD_DC_LIBS overrides the libraries; when UJSON_BUILD_DC_LIBS is set, the vendored .cc files are not compiled in. There is also UJSON_BUILD_NO_STRIP, which on Linux controls whether -Wl,--strip-all is passed to the linker. These are the escape hatches for distributions that want to build against a system double-conversion instead of the vendored one.
Installing ujson and getting a first encode out of it
The README gives one installation command. It pulls a wheel for your platform when one exists, and falls back to building the extension from source otherwise, which is when the double-conversion and compiler requirements come into play.
python -m pip install ujsonThe package metadata requires Python 3.10 or newer. Once installed, the module is imported as ujson and exposes dumps and loads. The README's own example, which uses the pycon prompt, is the shortest way to see both directions:
>>> import ujson
>>> ujson.dumps([{"key": "value"}, 81, True])
'[{"key":"value"},81,true]'
>>> ujson.loads("""[{"key": "value"}, 81, true]""")
[{'key': 'value'}, 81, True]You should see the compact form '[{"key":"value"},81,true]' printed, and then the list back as [{'key': 'value'}, 81, True]. Note what the first line does not contain: spaces after the colons or commas. ujson defaults to the tightest output, and indent defaults to 0, so pretty printing is opt-in.
The defaults are where a drop-in replacement stops being transparent. Three of them differ from what many callers expect. ensure_ascii defaults to True, so non-ASCII characters are escaped. escape_forward_slashes defaults to True, so a URL becomes "https:\\/\\/example.com". encode_html_chars defaults to False, which means angle brackets and ampersands pass through untouched unless you turn it on. If your consumer compares byte-for-byte against stdlib json output, these three keys are the first place to look.
The README documents each of these with the expected return value. For ensure_ascii, it shows ujson.dumps("åäö") returning '"\\u00e5\\u00e4\\u00f6"' and ujson.dumps("åäö", ensure_ascii=False) returning '"åäö"'. For escape_forward_slashes, it shows ujson.dumps("https://example.com") returning '"https:\\/\\/example.com"' and the same call with escape_forward_slashes=False returning '"https://example.com"'. For encode_html_chars, it shows ujson.dumps("<script>John&Doe", encode_html_chars=True) returning '"\\u003cscript\\u003eJohn\\u0026Doe"'. The README also notes that setting ensure_ascii to false is recommended when the destination format supports UTF-8, because it saves space.
Where ujson is the wrong choice, including its own benchmark table
The README's benchmark section is the clearest argument against adopting ujson today. On the test machine listed there, orjson is ahead of ujson in every row shown, often by a wide margin: for an array of 256 doubles, encode is 18,282 calls/sec for ujson against 79,569 for orjson, and decode is 28,765 against 93,283. The README does not hide this. It publishes it and then points readers at orjson anyway.
The maintenance-only policy is the second limitation, and it is a policy rather than a bug. Anything you want that is not a new Python version, a critical bug fix or a security fix will be rejected. That rules out new encoder options, new type support and performance work. If your roadmap assumes the library will grow with your needs, it will not.
There is a third, quieter constraint: this is a C extension. That means wheels per platform and per interpreter, a compiler when no wheel matches, and a C++ standard library dependency at link time. Environments that avoid native extensions entirely, or that need a pure-Python fallback path, are outside what ujson offers. The README does not document a pure-Python fallback.
Finally, the README does not document rollback, deprecation windows or a compatibility policy for the option keys. If you build on encode_html_chars or escape_forward_slashes, you are relying on behaviour that the maintenance-only statement says will not change, which cuts both ways: it will not break, and it will not improve either.
orjson as the alternative, and what actually differs
The README names orjson directly and describes it as "both much faster and less likely to introduce a surprise buffer overflow vulnerability in the future". That is the project's own recommendation, not an outside opinion, and it should carry weight.
The difference in approach is not just speed. orjson is a Rust extension, which is the reason the maintainers cite for the lower risk of buffer overflow. That is a statement about memory safety in the implementation language, and it applies to the class of bugs the README worries about, not to every possible failure. The benchmark table in the ujson README shows orjson ahead across the rows it reports, with the widest gaps on numeric arrays and on encode paths for simple values. The table also lists the versions used: ujson 5.7.1.dev26, orjson 3.9.0, simplejson 3.19.1 and json 2.0.9, on CPython 3.11.3.
There is a second difference that matters more day to day: defaults. ujson's escape_forward_slashes=True and ensure_ascii=True are documented behaviours you may already depend on. Moving to orjson means re-checking output against whatever consumes your JSON, because the escaping conventions differ and the migration is not a one-line import swap in every codebase.
simplejson and the standard library json appear in the same benchmark table as the slower baselines. For small payloads, or for code paths where JSON is not hot, the standard library remains the option with no build step and no platform matrix.
Maintenance cost, licence and the version you are pinning
Maintenance status is stated plainly by the project. The last push to the repository was on 2026-09-04, and the release 6.0.0 is dated 2026-09-04. The README's project status warning governs what that activity means: new Python versions, critical bugs and security issues are in scope, everything else is rejected. Treat the dependency as fixed-cost rather than improving.
Upgrade cost is mostly interpreter coverage. The classifiers in pyproject.toml list Python 3.10 through 3.15, plus CPython, GraalPy and PyPy implementations. When a new Python release appears, the project says support will be added, so the upgrade path is a version bump rather than a fork. The risk sits in the gap between a Python release and the matching wheel: building from source pulls in the vendored double-conversion sources and the -lstdc++ link flag unless you override them with UJSON_BUILD_DC_INCLUDES and UJSON_BUILD_DC_LIBS.
The licence field in pyproject.toml reads "BSD-3-Clause AND TCL", with LICENSE.txt as the licence file. The repository's licence metadata is reported as NOASSERTION, which is a metadata classification rather than a different licence text. The TCL component is not explained in the README, and this article cannot tell you what it covers. If your organisation has a licence review process, the file to hand to it is LICENSE.txt, and the question to ask is which parts of the tree fall under which of the two licences. That is a question for your reviewers, not something to settle from a package metadata string.
Editorial conclusion
Adopt ujson only where you already depend on its exact output conventions, such as escape_forward_slashes=True or ensure_ascii=True, and where a C extension is acceptable. Do not adopt it for new code that can take a dependency on orjson, and do not expect feature work: the README states that all changes other than new Python version support, critical bugs and security fixes will be rejected. Before committing, verify two things on your own data: that ujson.dumps output matches what your downstream consumer expects for slashes and non-ASCII characters, and that your target interpreter is covered by the classifiers in pyproject.toml, which currently run from Python 3.10 through 3.15.
Frequently asked questions
What is ujson?
ujson, or UltraJSON, is a JSON encoder and decoder written in C with bindings for Python. The README describes it as a drop-in replacement for most other JSON parsers, and it is installed with python -m pip install ujson.
Is orjson faster than ujson?
Yes, according to the benchmark table in the ujson README. On the machine listed there, orjson leads ujson in every row reported, and the README's project status section recommends migrating to orjson.
Does ujson still get new features?
No. The README states the library is in maintenance-only mode: new Python versions, critical bugs and security issues will still be fixed, but all other changes will be rejected.
Which Python versions does ujson support?
The classifiers in pyproject.toml list Python 3.10 through 3.15, and the project metadata requires Python 3.10 or newer. CPython, GraalPy and PyPy are all listed as supported implementations.
Why does ujson escape forward slashes in my URLs?
escape_forward_slashes defaults to True, so a URL is emitted as "https:\\/\\/example.com". The README documents passing escape_forward_slashes=False to get the unescaped form.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ultrajson-ultrajson)