Skip to content
JSON2026-08-286 min read

JSON Shows Chinese as \uXXXX Escapes After Formatting — How to Get Readable Text Back

Symptom: Chinese turned into \uXXXX

You paste a JSON containing Chinese into a formatter, and the output shows something like this:

{
  "name": "\u4e2d\u6587",
  "city": "\u5317\u4eac"
}

Instead of "北京" you see a string of \uXXXX tokens. Many people assume it is "garbled" or the tool is broken. It is not — this is a perfectly valid JSON Unicode escape, and the data is not corrupted.

The reverse is also common: logs full of \uXXXX that you need to compare against a human-readable sample in an API doc. Figuring out who escapes and who unescapes saves more time than randomly changing charset headers.

Why this escape appears

The JSON string spec allows any Unicode character to be written as \uXXXX (a 4-digit hex code point). The problem is that the producer of the JSON defaulted to "ASCII-only" output:

Language / libraryDefaultEscapes Chinese?
Python json.dumps()ensure_ascii=True (default)✅ Escapes to \uXXXX
Java JacksonESCAPE_NON_ASCII (common default)✅ Escapes
PHP json_encode (without JSON_UNESCAPED_UNICODE)Escapes✅ Escapes
JavaScript JSON.stringify()Does not escape❌ Keeps Chinese
Go encoding/jsonDoes not escape❌ Keeps Chinese

So \u5317\u4eac is just the UTF-16 code-point representation of "北京". Same data, different display form.

RFC 8259 permits raw UTF-8 in strings — {"name":"李帅"} is valid everywhere modern. Many serializers still default to \uXXXX for legacy transport channels that mangled 8-bit bytes, and for "safer" consumers. The modern posture: emit UTF-8 literals (readable diffs; roughly 3 bytes for CJK vs 6 characters for escapes), and escape only what the spec requires (quote, backslash, control characters).

How to get readable text back

Method 1: one-click with an online tool

Open the JSON Formatter on ToolVault and paste your JSON:

  1. Leave "Escape non-ASCII" off (default) — format or minify shows Chinese as normal literals (北京), because the tool parses then stringifies (a lossless round-trip);
  2. If you need \uXXXX output for an ASCII-only downstream, enable "Escape non-ASCII" and format or minify again;
  3. Everything is parsed locally in your browser — no data is uploaded to any server.

Note: JSON.parse() already converts escapes back to characters automatically. The real issue is the producer defaulting to escaping; the formatter just shows the raw content faithfully when you ask it to. For a single string that is not a full JSON document, use the Unicode encoder/decoder to inspect code points and \uXXXX forms.

Method 2: avoid the escape at the code level

If you generate the JSON yourself, just turn ASCII-only off:

import json
data = {"name": "中文", "city": "北京"}
# Defaults to escaping as \uXXXX
print(json.dumps(data))
# Turn escaping off to keep Chinese
print(json.dumps(data, ensure_ascii=False))
const data = { name: '中文', city: '北京' };
// JSON.stringify keeps Chinese by default, no escaping
console.log(JSON.stringify(data));
// Jackson: disable non-ASCII escaping
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, false);
String json = mapper.writeValueAsString(data);
// PHP 7+: keep Unicode literals
echo json_encode($data, JSON_UNESCAPED_UNICODE);

When you should keep the escape

Keeping \uXXXX is actually safer in some cases:

  • Logs / config files that must stay ASCII-only for legacy systems;
  • Cross-language transport where the receiver has weak UTF-8 support;
  • Avoiding secondary mojibake from mismatched BOM / encoding declarations.

Rule of thumb: human reads it → convert to Chinese; machine needs stability → keep the escape. Do not mix both representations on the same boundary — pick one, and validate with a canary string such as 李帅✓.

Escape or literal: what the spec says, what tools do

JSON's spec (RFC 8259) permits raw UTF-8 in strings. Yet many serializers default to \uXXXX for non-ASCII: Java's Jackson, PHP's json_encode without flags, older .NET configs. The motivations are legacy and safety. The bug pattern readers hit most often is double-decoding — treating an already-literal string as if it needed another unescape pass — which corrupts both forms. Fix it by choosing one representation per boundary.

The surrogate pair trap for rare characters

BMP Chinese characters (the common ~20K) fit one \uXXXX escape. Characters outside the BMP — many emoji, rare CJK ideographs — need surrogate pairs: \ud840\udc00, two escapes forming one character. The classic bug: code that decodes escapes one-at-a-time produces two garbage characters; string length checks report 2 where humans see 1 (breaking truncation at "20 chars"). Modern engines handle pairs internally (JS string iteration, Python 3); the danger zone is hand-rolled escape processors and length-sensitive truncation. Test escape-handling code with an astral-plane character, not just 中文.

Encoding cascade debugging: the five-layer checklist

Garbled Chinese in a JSON pipeline passes through five layers — source encoding, serializer setting, transport (HTTP charset header), storage (database/table charset), consumer decode — and the garbage pattern identifies the culprit:

PatternLikely layer
???Lossy conversion already happened; data is dead upstream
Mojibake like 中文UTF-8 bytes decoded as Latin-1/GBK — usually recoverable
Visible \uXXXX in the outputDouble-encoding, or a display layer that never parsed

Walk from source to consumer naming the layer before you change charset headers at random — that is how one corruption becomes two.

FAQ

Is \uXXXX mojibake?

No. It is the standard escape form of a Unicode code point and any JSON parser restores it losslessly. Real mojibake is usually 中文 from a GBK/UTF-8 mismatch.

Why does it look fine in the browser but escapes in the tool?

The source you copied (backend API, log) was likely ASCII-only output. A tool that shows the raw text faithfully will show escapes; format once with the JSON Formatter (Escape non-ASCII off) to get readable Chinese.

Does converting change the data?

No. Escaped and unescaped are two text representations of the same data; the parsed strings are identical. You can confirm with JSON Diff: same structure, different string spelling, same semantics.

I only want to escape one field

Use the formatter's global toggle for a full document; use the Unicode encoder/decoder for a single string. Avoid hand-building \u fragments in application code — surrogate pairs and hex casing are easy to get wrong.

Implementation notes

When reviewing escape changes with JSON Diff, numeric fidelity is not a concern: parsing runs in a lossless mode that preserves big-integer raw text, equal-value big integers compare as equal by that raw text, and differing instance references don't count as differences — the false positive where two identical long IDs get reported as modified was specifically fixed before release.


Provided by ToolVault. More JSON tools: JSON Formatter, JSON Diff, JSONPath Query, JSON Schema Validator, Unicode encoder/decoder. Visit the home page for more developer tools.