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 Formatter

json_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']; // Paris

The 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 argumentJSON object becomesAccess styleWorks with
false (default)stdClass$obj->keyOOP code, object type hints
trueAssociative 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() constantMeaning
JSON_ERROR_NONENo error — decode succeeded
JSON_ERROR_DEPTHMaximum nesting depth exceeded (default: 512)
JSON_ERROR_STATE_MISMATCHInvalid or malformed JSON
JSON_ERROR_CTRL_CHARUnexpected control character in JSON
JSON_ERROR_SYNTAXSyntax error (most common failure)
JSON_ERROR_UTF8Malformed UTF-8 characters
JSON_ERROR_RECURSIONRecursive 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']; // Alice

Using 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']; // 123

Common 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 &amp; 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
);
FlagPHP versionEffect
JSON_PRETTY_PRINT5.4+Adds whitespace and newlines for readability
JSON_UNESCAPED_UNICODE5.4+Outputs multibyte chars as-is instead of \uXXXX
JSON_UNESCAPED_SLASHES5.4+Does not escape forward slashes
JSON_THROW_ON_ERROR7.3+Throws JsonException instead of returning false
JSON_FORCE_OBJECT5.3+Encodes indexed arrays as JSON objects
JSON_NUMERIC_CHECK5.3+Encodes numeric strings as numbers
JSON_HEX_TAG5.3+Encodes < and > as Unicode escapes
JSON_HEX_AMP5.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