Saving UTF-8 texts with json.dumps as UTF-8, not as a \u escape sequence
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Pass ensure_ascii=False to json.dumps() to keep non-ASCII characters as readable Unicode instead of \u escape sequences. When writing that output to a file, also specify encoding="utf-8" in the open() call. These are two separate steps: one controls the JSON text representation, the other controls the byte encoding on disk.
Why json.dumps() Escapes Non-ASCII by Default
By default, json.dumps() converts every non-ASCII character to a \uXXXX escape sequence. This is not a bug. The JSON specification (RFC 8259) allows both escaped and unescaped Unicode, and the Python standard library defaults to the escaped form because it produces output that is safe to transmit through systems that only support ASCII.
Output:
The data is not corrupted. Any conforming JSON parser will decode こんにちは back to the original Japanese text. But the escaped form is difficult for humans to read and inflates file size.
The Fix: ensure_ascii=False
Setting ensure_ascii=False tells the JSON encoder to emit actual Unicode characters instead of escape sequences.
Output:
The output is still valid JSON. The only difference is representation. Both forms decode to identical Python objects.
Writing Readable JSON to a File
json.dumps() returns a Python str object, not bytes. When you write that string to a file, you need to specify the encoding. If you do not, Python uses the system default encoding, which may not be UTF-8 on all platforms (notably Windows, where the default is often cp1252).
Note the use of json.dump() (no s) when writing directly to a file handle. This is equivalent to calling json.dumps() and then f.write(), but slightly more efficient because it avoids creating the entire string in memory for large objects.
What Happens When You Forget encoding="utf-8"
This error only manifests on systems where the default encoding is not UTF-8. On Linux and macOS (which default to UTF-8), the code works without the explicit encoding parameter, but adding it is still the right practice for portable code.
Reading the File Back
When reading a UTF-8 JSON file, specify the same encoding:
If the file was written with ensure_ascii=True (the default), you do not need encoding="utf-8" to read it because the file contains only ASCII characters. But specifying UTF-8 on both read and write is a safe default that handles both cases.
Sending JSON as Bytes Over the Network
When transmitting JSON over HTTP, WebSockets, or message queues, you typically need bytes, not a Python string. Encode the string explicitly:
Many HTTP libraries (like requests) handle this encoding automatically when you pass a dict or string. But when working with lower-level APIs (sockets, raw WSGI), you need to manage the encoding yourself.
Combining with Other json.dumps() Options
ensure_ascii=False works with all other json.dumps() parameters:
Output:
Comparison Table
| Scenario | ensure_ascii | File encoding | Result |
| Default behavior | True (default) | System default | ASCII-safe JSON, \u escapes for non-ASCII |
| Readable file output | False | "utf-8" | Human-readable Unicode, portable across platforms |
| Network transport | False | N/A (use .encode("utf-8")) | Compact UTF-8 bytes |
| Legacy system compatibility | True (default) | Any | Safe for ASCII-only systems |
| Windows file output without encoding | False | System default (cp1252) | UnicodeEncodeError for CJK/emoji characters |
Handling Special Cases
Emoji and Characters Outside the BMP
Characters outside the Basic Multilingual Plane (like emoji) require surrogate pairs in the escaped form. ensure_ascii=False avoids this complexity entirely:
Mixed Content with Already-Escaped Strings
If your input data already contains literal \u sequences as part of the string content (not as Unicode characters), ensure_ascii=False does not affect them. The backslash is a regular character in the string and will be preserved:
Custom Encoders
If you use a custom JSONEncoder subclass, ensure_ascii=False still applies to the final output:
Common Pitfalls
- Setting
ensure_ascii=Falsebut forgettingencoding="utf-8"in theopen()call. On Windows, this causesUnicodeEncodeErrorfor any character outside the system code page. - Assuming
\uescape sequences in JSON output mean the data is corrupted. Both the escaped and unescaped forms are valid JSON and decode to identical values. - Using
ensure_ascii=Falsewhen writing JSON that will be consumed by an ASCII-only system. In that case, the default escaped form is the correct choice. - Confusing
json.dumps()(returns a string) withjson.dump()(writes to a file). Usedump()when writing to a file handle to avoid building the entire string in memory. - Calling
.encode("utf-8")on the result ofjson.dumps()and then writing it to a text-mode file. Either write bytes to a binary-mode file ("wb") or write the string to a text-mode file ("w") with the encoding parameter. Do not mix the two. - Pretty-printing with
indentbut leavingensure_asciiat the default. The file is readable in structure but not in content, which defeats the purpose of pretty-printing.
Summary
json.dumps(data, ensure_ascii=False)keeps Unicode characters readable instead of escaping them as\uXXXX.- Always pair
ensure_ascii=Falsewithencoding="utf-8"when writing to files for cross-platform compatibility. - Use
json.dump()(withouts) to write directly to a file handle instead of building the full string in memory. - Use
.encode("utf-8")when you need bytes for network transport. - Both escaped and unescaped JSON are valid and decode to identical data. The choice is about readability and compatibility, not correctness.

