Skip to content
Carlo Fontanos
Carlo Fontanos

Carlo Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

json_encode() Has Flags That Fix Its Worst Defaults

C
Carlo Fontanos
· 2 min read

Run json_encode(['url' => 'https://example.com/café']) and look closely at what comes out:

{"url":"https:\/\/example.com\/café"}

Escaped slashes nobody asked for, unicode exploded into \u sequences. Both are legacy defaults (the slash-escaping guarded against </script> injection in inline HTML contexts; the unicode escaping catered to ancient latin-1 pipelines), and both make output larger and harder to read for no modern benefit.

The flags I consider mandatory

$json = json_encode($data,
    JSON_THROW_ON_ERROR
    | JSON_UNESCAPED_UNICODE
    | JSON_UNESCAPED_SLASHES
);

JSON_THROW_ON_ERROR is the important one. Without it, json_encode returns false on failure (say, invalid UTF-8 from a scraped string or a NAN float) - and false quietly becomes the string "false" or an empty body somewhere downstream, failing far from the cause. With the flag, you get a JsonException at the actual crime scene. Same flag works on json_decode, where the silent-null failure mode has burned even more people.

UNESCAPED_UNICODE: "café" stays "café". Smaller payloads, readable logs, and correct behavior for every language that isn't ASCII.

UNESCAPED_SLASHES: URLs look like URLs. (If you're echoing JSON directly into an inline <script> block, keep slash escaping or - better - use JSON_HEX_TAG which escapes < and > specifically for that context.)

Situational flags worth knowing

  • JSON_PRETTY_PRINT - for humans: config files, debug dumps, API explorer output. Never for production payloads (size).
  • JSON_PRESERVE_ZERO_FRACTION - keeps 10.0 as 10.0 instead of 10. Matters when the consumer is typed (Java, Swift, strict JSON schema) and suddenly gets an int where it expected a double.
  • JSON_PARTIAL_OUTPUT_ON_ERROR - the opposite philosophy to THROW: substitute nulls for unencodable values and keep going. Right choice in exactly one place: logging pipelines, where losing one field beats losing the log line.
  • JSON_INVALID_UTF8_SUBSTITUTE - replaces broken byte sequences with U+FFFD instead of failing. Pair with scraped or user-supplied content.

Decode-side companions

$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
// associative arrays, sane depth, loud failures

And for money or other precision-critical decimals: JSON_BIGINT_AS_STRING on decode keeps 64-bit ids from silently becoming floats.

Wrap your preferred flag combo in a two-line helper (json_out(), json_in()) and never think about it again - the same "fix the defaults once" move as letting URLSearchParams own query strings in JS. Defaults from 2006 shouldn't be making decisions in 2026.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Your details are only used to reply to you.

Keep reading