PHP json_encode() and json_decode()

JSON (JavaScript Object Notation) is a lightweight, text-based data format used almost everywhere data moves between systems: APIs, configuration files, browser storage, and log files. PHP ships with two built-in functions, json_encode() and json_decode(), that convert PHP values into JSON text and back again. Mastering them is essential for building APIs, consuming third-party services, and talking to JavaScript frontends, since JSON is the de facto standard for exchanging structured data on the web.

Overview: How JSON Encoding and Decoding Work

PHP has included JSON support in core since PHP 5.2, via the bundled json extension, a fast C parser and serializer. When you call json_encode(), PHP walks the value you pass in, recursively if it is an array or object, and produces a JSON-formatted string that represents the same data using JSON’s own type system: objects, arrays, strings, numbers, booleans, and null. json_decode() does the reverse: it parses a JSON string with the C parser and builds PHP values (arrays, stdClass objects, scalars) that mirror the JSON structure.

Because PHP and JSON do not have identical type systems, both functions make some type-mapping decisions on your behalf. For example, JSON has only one array-like syntax, so json_encode() has to decide whether a PHP array should become a JSON array ([...]) or a JSON object ({...}). It does this by inspecting the array’s keys: a sequential, zero-based list of integer keys becomes a JSON array; anything else, such as string keys or non-sequential integers, becomes a JSON object. On the way back in, json_decode() defaults to building stdClass objects for JSON objects, but you can ask it to build associative arrays instead.

Both functions accept a flags bitmask that changes their behavior, such as formatting, error handling, and encoding choices, plus a depth parameter that limits how many levels of nesting are allowed. This protects your application from being crashed by an excessively deep or maliciously crafted structure.

Syntax

json_encode(mixed $value, int $flags = 0, int $depth = 512): string|false

json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed
Parameter Function Meaning
$value json_encode Any encodable PHP value: array, object, string, int, float, bool, or null.
$json json_decode A JSON-formatted string to parse.
$associative json_decode When true, JSON objects become associative arrays; when false or omitted, they become stdClass objects.
$depth both Maximum nesting depth allowed, default 512.
$flags both A bitmask of JSON_* constants that change formatting or error behavior.

Useful flags include JSON_PRETTY_PRINT (adds indentation and line breaks), JSON_UNESCAPED_SLASHES (stops forward slashes from being escaped), JSON_UNESCAPED_UNICODE (keeps multi-byte characters as literal UTF-8 instead of escape sequences), JSON_THROW_ON_ERROR (throws a JsonException instead of returning false or null on failure), and JSON_OBJECT_AS_ARRAY (equivalent to passing true as $associative to json_decode()).

Examples

Example 1: Encoding an associative array

<?php
$user = [
    "name" => "Alice",
    "age" => 30,
    "active" => true,
    "roles" => ["admin", "editor"]
];

$json = json_encode($user);

echo $json;

Output:

{"name":"Alice","age":30,"active":true,"roles":["admin","editor"]}

The associative array becomes a JSON object because its keys are strings. Notice that the PHP true boolean becomes the JSON literal true, and the nested list roles becomes a JSON array because its keys are sequential integers starting at zero.

Example 2: Decoding as an object vs. an associative array

<?php
$json = '{"name":"Bob","age":25,"skills":["PHP","SQL"]}';

$asObject = json_decode($json);
$asArray = json_decode($json, true);

echo $asObject->name . "\n";
echo $asArray["name"] . "\n";
echo $asObject->skills[0] . "\n";
echo $asArray["skills"][1] . "\n";

Output:

Bob
Bob
PHP
SQL

The same JSON string is decoded twice. Without the second argument, json_decode() returns a stdClass object, so properties are accessed with ->. Passing true tells it to build nested associative arrays instead, accessed with square brackets. Both represent identical data, so pick whichever access style fits the rest of your code.

Example 3: Pretty-printing a list of records

<?php
$products = [
    ["id" => 101, "name" => "Keyboard", "price" => 49.99],
    ["id" => 102, "name" => "Mouse", "price" => 19.99],
];

$json = json_encode($products, JSON_PRETTY_PRINT);

echo $json;

Output:

[
    {
        "id": 101,
        "name": "Keyboard",
        "price": 49.99
    },
    {
        "id": 102,
        "name": "Mouse",
        "price": 19.99
    }
]

Because the outer array has sequential integer keys, it encodes as a JSON array of objects. The JSON_PRETTY_PRINT flag adds four-space indentation and line breaks, which is handy for debugging or writing readable configuration files, but adds bytes that are wasted on a production API response.

Example 4: Handling decode failures with JSON_THROW_ON_ERROR

<?php
$malformed = '{"name": "Charlie", "age": }';

try {
    $data = json_decode($malformed, true, 512, JSON_THROW_ON_ERROR);
    echo "Decoded successfully\n";
} catch (JsonException $e) {
    echo "JSON error: " . $e->getMessage() . "\n";
}

Output:

JSON error: Syntax error

The JSON string is missing a value after the age key, so parsing fails. Without JSON_THROW_ON_ERROR, json_decode() would silently return null, leaving you to remember to check json_last_error(). With the flag, PHP throws a JsonException that you can catch like any other exception, which fits naturally into modern error-handling code.

How It Works Step by Step (Under the Hood)

When json_encode() runs, PHP’s engine walks the value depth-first and applies this type mapping:

PHP value JSON output
Sequential array (keys 0, 1, 2, …) JSON array [...]
Associative array (string or non-sequential keys) JSON object {...}
Object with public properties JSON object of the public properties only
Object implementing JsonSerializable Whatever jsonSerialize() returns
null null
true / false true / false
int / float JSON number
string JSON string, with quotes, backslashes, and control characters escaped

Every string PHP writes into JSON must be valid UTF-8; if it is not, json_encode() fails and returns false. Numbers that exceed PHP’s integer range are converted to floats unless the JSON_BIGINT_AS_STRING flag is used during decoding, in which case oversized integers are kept as strings so no precision is lost.

When json_decode() runs, the C parser tokenizes the JSON text and builds PHP values as it goes: JSON objects become stdClass instances (or arrays, if you asked for that), JSON arrays become PHP arrays, and JSON numbers become PHP int or float depending on whether they contain a decimal point or exponent. If the text is not valid JSON, or nesting exceeds $depth, decoding fails: json_decode() returns null and records an error code that json_last_error() can retrieve, unless JSON_THROW_ON_ERROR is set, in which case a JsonException is thrown immediately instead.

Common Mistakes

Mistake 1: Checking for failure with a loose comparison

Valid JSON can decode to false, 0, or an empty string, all of which are falsy in PHP. Checking failure with a loose equality comparison against false treats a perfectly valid result as an error.

<?php
$json = 'false';
$data = json_decode($json);

if ($data == false) {
    echo "Decoding failed\n";
} else {
    echo "Decoded successfully\n";
}

Output:

Decoding failed

This is wrong: the JSON text false decoded correctly to the PHP boolean false, but the loose comparison treats that as a failure. The fix is to check json_last_error() instead of inspecting the decoded value itself:

<?php
$json = 'false';
$data = json_decode($json);

if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
    echo "Decoding failed\n";
} else {
    echo "Decoded successfully\n";
}

Output:

Decoded successfully

Mistake 2: Assuming all object properties get encoded

json_encode() only serializes an object’s public properties by default. Private and protected properties are silently skipped, which surprises developers who expect a full dump of the object.

<?php
class UserAccount
{
    public string $username;
    private string $passwordHash;

    public function __construct(string $username, string $passwordHash)
    {
        $this->username = $username;
        $this->passwordHash = $passwordHash;
    }
}

$user = new UserAccount("dana99", "a1b2c3");

echo json_encode($user);

Output:

{"username":"dana99"}

Here the password hash vanished from the output because it is private, not because PHP is protecting it for security reasons, simply because json_encode() cannot see it via reflection of public state. The reliable fix is to implement the JsonSerializable interface, which gives you full control over exactly what gets serialized:

<?php
class UserAccount implements JsonSerializable
{
    public function __construct(
        public string $username,
        private string $passwordHash,
        public string $email
    ) {}

    public function jsonSerialize(): array
    {
        return [
            "username" => $this->username,
            "email" => $this->email,
        ];
    }
}

$user = new UserAccount("dana99", "a1b2c3", "dana99@example.com");

echo json_encode($user);

Output:

{"username":"dana99","email":"dana99@example.com"}

Best Practices

  • Pass true as the second argument to json_decode(), or use the JSON_OBJECT_AS_ARRAY flag, whenever you plan to use array syntax; mixing stdClass and array access is a common source of bugs.
  • Always check for errors. Prefer JSON_THROW_ON_ERROR on both functions in PHP 7.3+ so a malformed value raises an exception instead of silently returning false or null.
  • Never check decode success by testing the decoded value for truthiness; use json_last_error() === JSON_ERROR_NONE instead, since valid JSON can legitimately decode to false, 0, or null.
  • Reach for JSON_PRETTY_PRINT for debug output, log files, or hand-edited config, not for high-traffic API responses, where the extra whitespace is wasted bandwidth.
  • Use JSON_UNESCAPED_UNICODE and JSON_UNESCAPED_SLASHES when your data contains non-ASCII text or URLs, so the output stays compact and human-readable.
  • Implement JsonSerializable on domain objects instead of relying on default public-property serialization; it lets you exclude sensitive fields and rename keys deliberately.
  • Set the Content-Type: application/json header when returning JSON from an HTTP endpoint, so clients parse the response correctly.
  • Treat decoded data from external sources as untrusted input: validate the shape and types of the resulting array or object before using it, just as you would with any user-supplied data.

Practice Exercises

  1. Build an associative array describing a blog post with title, author, an array of tags, and a published boolean, then echo it as pretty-printed JSON using JSON_PRETTY_PRINT.
  2. Given a JSON string describing an order with a customer object and an items array, where each item has price and quantity, decode it as an associative array and calculate the order total by summing price times quantity for every item.
  3. Write a Temperature class with a single celsius property that implements JsonSerializable so that encoding an instance always produces both a celsius key and a computed fahrenheit key.

Summary

  • json_encode() converts a PHP value into a JSON string; json_decode() converts a JSON string back into a PHP value.
  • Sequential arrays become JSON arrays; associative arrays and public object properties become JSON objects.
  • By default, json_decode() returns stdClass objects; pass true as the second argument to get associative arrays instead.
  • Both functions accept a flags bitmask, such as JSON_PRETTY_PRINT, JSON_UNESCAPED_UNICODE, and JSON_THROW_ON_ERROR, plus a depth limit for nested structures.
  • Never assume a decode succeeded just because the result is truthy; check json_last_error() or use JSON_THROW_ON_ERROR.
  • Implement JsonSerializable when you need precise control over how an object is represented in JSON.