Converting JSON to a PHP Array Without a json_decode Round-Trip
How to turn JSON into a clean PHP array literal: objects become => associative arrays, indexed arrays, nested structures, and Laravel or WordPress config files.
Converting JSON to a PHP Array Without a json_decode Round-Trip
Every PHP developer hits this moment: a package ships its defaults as JSON, an API hands you a sample payload, or a plugin stored its settings as a JSON blob in wp_options. You don't want that JSON at runtime — you want a real PHP array literal sitting in a .php file, so config() and require can pick it up the way the framework expects. Typing forty keys by hand and praying you didn't fat-finger 'tiemout' is how bugs get born.
The mechanical translation is small but full of traps. The key one: a JSON object becomes a PHP associative array with 'key' => value pairs, while a JSON array becomes a plain indexed array. Get the quoting or the escaping slightly wrong and the file either fails to parse or, worse, parses into something subtly different from what you intended.
Objects become => associative arrays
This is the rule that does most of the work. A JSON object maps to a PHP associative array, and every key is emitted as a single-quoted string with the => arrow:
{
"app": {
"name": "Toolora",
"debug": false,
"providers": ["cache", "queue"],
"limits": { "0": "free", "1": "pro" }
}
}
becomes:
[
'app' => [
'name' => 'Toolora',
'debug' => false,
'providers' => [
'cache',
'queue',
],
'limits' => [
'0' => 'free',
'1' => 'pro',
],
],
]
Notice three things. The outer object and the nested app object both became associative arrays. The providers value was a JSON array, so it came out as an indexed array — no keys, just values. And limits kept its keys quoted as '0' and '1' even though they look like integers. That quoting matters more than it appears, which is the next point.
Why numeric keys stay quoted
A JSON key is always a string. So "0" is the string "0", not the number zero. If you let it render as an unquoted PHP key (0 => 'free'), PHP silently coerces it to the integer key 0. Then a later lookup with the string key '0'... well, PHP normalizes that one too, so the two happen to match — but the moment your keys are something like "007" or "1.5", the integer-coercion rules diverge and a string lookup misses. Emitting '0' => … every time is the safe default: the array you get behaves exactly like the JSON you put in.
This is also the most common thing people hand-edit and regret. If you go in and strip the quotes off a numeric key to make it "look cleaner," you've changed the key's type. Leave the quotes unless you specifically want integer keys.
How this differs from json_decode
You might ask: why not just json_decode($json, true) at runtime? Because that builds the array every time the file loads, and it keeps your data trapped in a JSON string instead of as readable, diff-able, IDE-navigable PHP. A config file should be a config file — a literal array PHP can require directly, with no parse step on the hot path.
The values you get from a JSON-to-PHP conversion match exactly what json_decode($json, true) would produce — same nesting, same scalar types, same associative-vs-indexed shape. The difference is purely when and how: the converter does the translation once, at authoring time, and gives you source code. json_decode does it at runtime and gives you an in-memory value. For a captured API response you're turning into a test fixture, the static literal is what you want.
People also reach for PHP's var_export(). It's close, but it only ever emits the legacy array(...) syntax, indents in its own fixed style, and runs server-side — so you'd have to spin up a PHP process and round-trip the JSON through it. The JSON to PHP array converter gives you the modern short [] form, a configurable 2 or 4-space indent, optional trailing commas, and a <?php return …; wrapper, all in the browser with no PHP runtime at all.
Escaping: single quotes, backslashes, and that \n trap
The output uses single-quoted PHP strings, where exactly two characters are special. A backslash becomes \\ and a single quote becomes \'. Nothing else is touched. That keeps a Windows path and an apostrophe honest:
it's→'it\'s'C:\Users\dev→'C:\\Users\\dev'
The escaping order matters: backslashes are escaped first, then quotes, so the backslash added in front of a quote never gets doubled by accident. And here's the trap people fall into — a \n inside a single-quoted PHP string is a literal backslash followed by n, not a newline. That's correct PHP behaviour, not a bug. If you genuinely need a line break in a value, the JSON itself must contain a real newline character, which renders verbatim inside the single quotes.
A Laravel config file, start to finish
Here's where I actually use this most. A while back I was integrating a payment package whose defaults shipped as a JSON document — endpoints, retry counts, a nested map of currency settings. I needed a config/payments.php that returned an array so config('payments.endpoints.refund') would resolve. I pasted the JSON, switched the <?php return …; wrapper on, set the indent to 4 to match Laravel's house style, and left short syntax on. The output dropped straight into the file and resolved on the first run. No hand-typing, no typo'd key, no second guess about whether a nested currency map came out as associative.
The recipe for a Laravel config:
- Paste the JSON.
- Turn on the
<?php return …;wrapper — this gives you<?php\n\nreturn [ … ];, exactly the shapeconfig()andrequireexpect. - Leave short
[]syntax on (PSR-12 and Laravel use it everywhere). - Set indent to 4 spaces.
- Add a trailing comma so the next person who edits the file gets a clean one-line Git diff.
For a WordPress migration the flow is nearly identical, except you usually leave the wrapper off — you're pasting the bare [ … ] literal into the middle of a migration function that feeds update_option(), not writing a standalone returnable file. The quoted numeric keys are doing quiet work here too: update_option stores '0' => … as you wrote it instead of reindexing the array out from under you.
One asymmetry to remember
PHP has no distinct empty-map literal. Both an empty JSON object {} and an empty JSON array [] render as []. Once it's [], PHP treats it as an indexed array. If a downstream consumer must see an associative shape, give it at least one key, or cast it with (object) in your PHP. It's a small thing, but it surprises people who expect a perfectly symmetric round-trip.
One more input-side note: the conversion uses strict JSON.parse, so comments, unquoted keys, and trailing commas in your input will error out. The trailing-comma option only affects the PHP output. If your source is JSON5 or has comments, clean it up first — running it through a JSON formatter is the quickest way to both validate it and strip the cruft before you convert.
Convert once, commit the result, and your config or fixture is plain readable PHP from then on — no runtime decode, no JSON string hiding your data.
Made by Toolora · Updated 2026-06-13