Parse JSON in PHP
PHP has built-in JSON support via json_decode() and json_encode() — no library or Composer package needed. Both functions have been part of the PHP core since PHP 5.2 (released in 2006) and cover everything from parsing a simple API response to serializing nested data structures back to JSON. This guide covers the full API: object vs associative array output, error handling with JSON_THROW_ON_ERROR (PHP 7.3+), reading JSON files, and all the useful json_encode() flags.
Validate your JSON in Jsonic before parsing it in PHP.
Open JSON Formatterjson_decode(): object vs associative array
By default, json_decode() converts JSON objects into PHP stdClass instances — you access properties with the -> operator. Pass true as the second argument (the $associative parameter in PHP 8.0+) to get PHP associative arrays instead, accessed with square-bracket notation.
<?php
$json = '{"id": 42, "name": "Alice", "roles": ["admin", "editor"]}';
// Default: returns stdClass object
$obj = json_decode($json);
echo $obj->name; // Alice
echo $obj->roles[0]; // admin
// Pass true: returns associative array
$arr = json_decode($json, true);
echo $arr['name']; // Alice
echo $arr['roles'][0]; // admin
// PHP 8.0+ named argument (same result, more readable)
$arr = json_decode($json, associative: true);
// Nested objects also become arrays when associative: true
$complex = '{"user": {"address": {"city": "Paris"}}}';
$data = json_decode($complex, true);
echo $data['user']['address']['city']; // ParisThe choice depends on your codebase style. Associative arrays (true) integrate naturally with PHP array functions like array_map(), array_filter(), and array_column(). stdClass objects work well with type-hinted functions that expect objects, and IDEs can provide auto-completion when you cast them to a typed class.
| Second argument | JSON object becomes | Access style | Works with |
|---|---|---|---|
false (default) | stdClass | $obj->key | OOP code, object type hints |
true | Associative array | $arr['key'] | array_* functions, foreach |
json_encode(): serialize PHP to JSON
json_encode() converts any PHP value — arrays, objects, scalars — into a JSON string. It returns false on failure (for example, if the input contains invalid UTF-8). The second parameter accepts a bitmask of flags that control the output format.
<?php
$data = [
'name' => 'Café René',
'price' => 12.5,
'tags' => ['food', 'french'],
'url' => 'https://example.com/menu',
'active' => true,
'deleted' => null,
];
// Basic encoding
$json = json_encode($data);
// {"name":"Caf\u00e9 Ren\u00e9","price":12.5,"tags":["food","french"],...}
// Pretty-print + preserve Unicode + keep slashes unescaped
$pretty = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
/*
{
"name": "Café René",
"price": 12.5,
"tags": [
"food",
"french"
],
"url": "https://example.com/menu",
"active": true,
"deleted": null
}
*/
// Encode a stdClass object — same as an array
$obj = new stdClass();
$obj->id = 1;
$obj->name = 'Alice';
echo json_encode($obj); // {"id":1,"name":"Alice"}
// Force objects to always encode as JSON objects (not arrays)
$emptyArr = [];
echo json_encode($emptyArr); // []
echo json_encode($emptyArr, JSON_FORCE_OBJECT); // {}Error handling: json_last_error() and JSON_THROW_ON_ERROR
json_decode() silently returns null on failure — it does not throw an exception by default. There are two patterns for detecting errors: calling json_last_error() after decoding (all PHP versions), or passing the JSON_THROW_ON_ERROR flag (PHP 7.3+, the recommended approach).
Pattern 1: json_last_error() (PHP 5.3+)
<?php
$json = '{"name": "Alice", INVALID}'; // malformed JSON
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$msg = json_last_error_msg(); // human-readable description
echo "JSON parse error: $msg";
// JSON parse error: Syntax error
}
// WARNING: json_decode() also returns null for the literal JSON value "null"
// Always check json_last_error() to tell them apart:
$nullJson = 'null';
$result = json_decode($nullJson, true);
if ($result === null && json_last_error() === JSON_ERROR_NONE) {
echo "JSON decoded successfully — value is null";
} elseif (json_last_error() !== JSON_ERROR_NONE) {
echo "Parse error: " . json_last_error_msg();
}Pattern 2: JSON_THROW_ON_ERROR (PHP 7.3+ — preferred)
<?php
// JSON_THROW_ON_ERROR makes json_decode() throw JsonException on failure
// instead of silently returning null — much easier to handle correctly.
function parseJson(string $json): mixed
{
try {
return json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
// $e->getMessage() contains the parse error description
// $e->getCode() matches the json_last_error() constants
throw new RuntimeException("Invalid JSON: " . $e->getMessage(), 0, $e);
}
}
// json_encode() also supports JSON_THROW_ON_ERROR
function toJson(mixed $data): string
{
try {
return json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
} catch (JsonException $e) {
throw new RuntimeException("JSON encoding failed: " . $e->getMessage(), 0, $e);
}
}
// Usage
$data = parseJson('{"id": 1, "name": "Alice"}');
echo $data['name']; // Alice| json_last_error() constant | Meaning |
|---|---|
JSON_ERROR_NONE | No error — decode succeeded |
JSON_ERROR_DEPTH | Maximum nesting depth exceeded (default: 512) |
JSON_ERROR_STATE_MISMATCH | Invalid or malformed JSON |
JSON_ERROR_CTRL_CHAR | Unexpected control character in JSON |
JSON_ERROR_SYNTAX | Syntax error (most common failure) |
JSON_ERROR_UTF8 | Malformed UTF-8 characters |
JSON_ERROR_RECURSION | Recursive reference in value passed to json_encode() |
Read a JSON file in PHP
The standard pattern is file_get_contents() + json_decode(). Use JSON_THROW_ON_ERROR so a bad file path or malformed JSON surfaces immediately as an exception rather than a silent null.
<?php
// ── Basic: read and decode a local JSON file ──────────────────────────────
$path = __DIR__ . '/config.json';
if (!file_exists($path)) {
throw new RuntimeException("File not found: $path");
}
$contents = file_get_contents($path);
if ($contents === false) {
throw new RuntimeException("Could not read file: $path");
}
$config = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
echo $config['database']['host']; // e.g. "localhost"
// ── Read a large JSON file line by line (JSON Lines / NDJSON format) ──────
// Each line is a separate, self-contained JSON object.
$handle = fopen(__DIR__ . '/events.ndjson', 'r');
if ($handle === false) {
throw new RuntimeException("Could not open events.ndjson");
}
while (($line = fgets($handle)) !== false) {
$line = trim($line);
if ($line === '') continue; // skip blank lines
$event = json_decode($line, true, 512, JSON_THROW_ON_ERROR);
echo $event['type'] . ': ' . $event['id'] . PHP_EOL;
}
fclose($handle);
// ── Write PHP data back to a JSON file ───────────────────────────────────
$settings = ['debug' => false, 'version' => '2.1.0', 'maxRetries' => 3];
$written = file_put_contents(
__DIR__ . '/settings.json',
json_encode($settings, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
if ($written === false) {
throw new RuntimeException("Could not write settings.json");
}For large files (hundreds of MB), avoid loading the entire file into memory with file_get_contents(). Use a streaming JSON parser such as halaxa/json-machine (Composer) instead — it processes JSON as a generator, keeping memory usage constant regardless of file size.
Parse JSON from an HTTP API response
PHP provides two common ways to make HTTP requests: the built-in file_get_contents() with a stream context, and the curl extension (available in virtually every PHP installation). Both return the response body as a string that you then pass to json_decode().
Using file_get_contents() with an HTTP stream context
<?php
$url = 'https://api.example.com/users/42';
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "Accept: application/json
Authorization: Bearer YOUR_TOKEN
",
'timeout' => 10,
],
]);
$body = file_get_contents($url, false, $context);
if ($body === false) {
throw new RuntimeException("HTTP request failed for: $url");
}
$user = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $user['name']; // AliceUsing cURL (recommended for production)
<?php
// ── GET request with cURL ─────────────────────────────────────────────────
function fetchJson(string $url, string $bearerToken = ''): array
{
$ch = curl_init($url);
$headers = ['Accept: application/json'];
if ($bearerToken !== '') {
$headers[] = "Authorization: Bearer $bearerToken";
}
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 10,
CURLOPT_FOLLOWLOCATION => true,
]);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($body === false || $curlErr !== '') {
throw new RuntimeException("cURL error: $curlErr");
}
if ($httpCode < 200 || $httpCode >= 300) {
throw new RuntimeException("HTTP $httpCode from $url");
}
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
$user = fetchJson('https://api.example.com/users/42', 'my-secret-token');
echo $user['email'];
// ── POST request sending JSON payload ────────────────────────────────────
function postJson(string $url, array $payload): array
{
$ch = curl_init($url);
$body = json_encode($payload, JSON_THROW_ON_ERROR);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
'Content-Length: ' . strlen($body),
],
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
}
$created = postJson('https://api.example.com/users', [
'name' => 'Bob',
'email' => 'bob@example.com',
]);
echo $created['id']; // 123Common json_encode() options
The second parameter of json_encode() accepts a bitmask of flag constants. You can combine multiple flags with the | (bitwise OR) operator. The third parameter sets the maximum nesting depth (default: 512).
<?php
$data = [
'city' => 'São Paulo',
'website' => 'https://example.com/path',
'html' => '<p>Hello & welcome</p>',
'price' => 9.99,
];
// JSON_PRETTY_PRINT — human-readable indentation
echo json_encode($data, JSON_PRETTY_PRINT);
// JSON_UNESCAPED_UNICODE — output UTF-8 chars directly (not \uXXXX)
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// "city":"São Paulo" instead of "city":"São Paulo"
// JSON_UNESCAPED_SLASHES — keep forward slashes unescaped
echo json_encode($data, JSON_UNESCAPED_SLASHES);
// "website":"https://example.com/path" instead of "https://example.com/path"
// JSON_HEX_TAG — escape < and > as < > (XSS safety in HTML context)
echo json_encode($data, JSON_HEX_TAG);
// JSON_NUMERIC_CHECK — encode numeric strings as numbers
$mixed = ['count' => '42', 'ratio' => '3.14'];
echo json_encode($mixed, JSON_NUMERIC_CHECK);
// {"count":42,"ratio":3.14}
// JSON_FORCE_OBJECT — encode all arrays as JSON objects (even indexed arrays)
echo json_encode(['a', 'b', 'c'], JSON_FORCE_OBJECT);
// {"0":"a","1":"b","2":"c"}
// Combine flags for a typical API response
$response = json_encode(
$data,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
);| Flag | PHP version | Effect |
|---|---|---|
JSON_PRETTY_PRINT | 5.4+ | Adds whitespace and newlines for readability |
JSON_UNESCAPED_UNICODE | 5.4+ | Outputs multibyte chars as-is instead of \uXXXX |
JSON_UNESCAPED_SLASHES | 5.4+ | Does not escape forward slashes |
JSON_THROW_ON_ERROR | 7.3+ | Throws JsonException instead of returning false |
JSON_FORCE_OBJECT | 5.3+ | Encodes indexed arrays as JSON objects |
JSON_NUMERIC_CHECK | 5.3+ | Encodes numeric strings as numbers |
JSON_HEX_TAG | 5.3+ | Encodes < and > as Unicode escapes |
JSON_HEX_AMP | 5.3+ | Encodes & as & |
Frequently asked questions
Why does json_decode() return null?
json_decode() returns null for two distinct reasons: the JSON string is malformed (e.g., a syntax error, trailing comma, or unquoted key), or the JSON input is literally the string "null". Because both cases produce a PHP null, you must always call json_last_error() after decoding — or use JSON_THROW_ON_ERROR (PHP 7.3+) — to distinguish a parse failure from a valid null value. Common causes of malformed JSON include single-quoted strings, trailing commas in objects or arrays, and BOM characters at the start of a file.
How do I get an associative array instead of an object from json_decode()?
Pass true as the second argument: json_decode($json, true). This makes json_decode() return PHP arrays for all JSON objects instead of stdClass instances. In PHP 8.0+, the parameter is named $associative, so you can also write json_decode($json, associative: true) for clarity. The full signature is json_decode(string $json, bool $associative = false, int $depth = 512, int $flags = 0).
How do I pretty-print JSON in PHP?
Pass the JSON_PRETTY_PRINT flag to json_encode(): json_encode($data, JSON_PRETTY_PRINT). You can combine multiple flags with the bitwise OR operator, for example json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) to also preserve Unicode characters and forward slashes without escaping. PHP uses 4-space indentation for JSON_PRETTY_PRINT — there is no built-in way to change this to 2 spaces without post-processing the output string.
Does PHP's json_decode() support comments in JSON?
No. PHP's json_decode() strictly follows the JSON specification (RFC 8259), which does not allow comments. If your JSON string contains // or /* */ comments, json_decode() will fail and return null. Strip comments before decoding using a regex, or switch to a format that supports comments such as JSON5 or JSONC handled by a separate Composer library.
How do I handle UTF-8 characters in json_encode()?
By default, json_encode() escapes non-ASCII characters as \uXXXX sequences. Pass the JSON_UNESCAPED_UNICODE flag to output UTF-8 characters directly: json_encode($data, JSON_UNESCAPED_UNICODE). Make sure your source strings are valid UTF-8 first — passing invalid UTF-8 to json_encode() causes it to return false (or throw JsonException if JSON_THROW_ON_ERROR is set). You can sanitize strings with mb_convert_encoding($str, 'UTF-8', 'UTF-8') before encoding.
What is the maximum nesting depth for json_decode()?
The default maximum nesting depth is 512 levels. You can change it via the third parameter: json_decode($json, true, 128) to lower the limit, or json_decode($json, true, 1024) to raise it. Exceeding the depth limit causes json_decode() to return null and json_last_error() to return JSON_ERROR_DEPTH. When using JSON_THROW_ON_ERROR, a depth exceeded error throws a JsonException with the message "Maximum stack depth exceeded".
Validate your JSON
Paste your JSON into Jsonic to catch syntax errors before parsing in PHP.
Open JSON Formatter