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
trueas the second argument tojson_decode(), or use theJSON_OBJECT_AS_ARRAYflag, whenever you plan to use array syntax; mixingstdClassand array access is a common source of bugs. - Always check for errors. Prefer
JSON_THROW_ON_ERRORon both functions in PHP 7.3+ so a malformed value raises an exception instead of silently returningfalseornull. - Never check decode success by testing the decoded value for truthiness; use
json_last_error() === JSON_ERROR_NONEinstead, since valid JSON can legitimately decode tofalse,0, ornull. - Reach for
JSON_PRETTY_PRINTfor debug output, log files, or hand-edited config, not for high-traffic API responses, where the extra whitespace is wasted bandwidth. - Use
JSON_UNESCAPED_UNICODEandJSON_UNESCAPED_SLASHESwhen your data contains non-ASCII text or URLs, so the output stays compact and human-readable. - Implement
JsonSerializableon 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/jsonheader 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
- Build an associative array describing a blog post with
title,author, an array oftags, and apublishedboolean, then echo it as pretty-printed JSON usingJSON_PRETTY_PRINT. - Given a JSON string describing an order with a
customerobject and anitemsarray, where each item haspriceandquantity, decode it as an associative array and calculate the order total by summingpricetimesquantityfor every item. - Write a
Temperatureclass with a singlecelsiusproperty that implementsJsonSerializableso that encoding an instance always produces both acelsiuskey and a computedfahrenheitkey.
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()returnsstdClassobjects; passtrueas the second argument to get associative arrays instead. - Both functions accept a
flagsbitmask, such asJSON_PRETTY_PRINT,JSON_UNESCAPED_UNICODE, andJSON_THROW_ON_ERROR, plus adepthlimit for nested structures. - Never assume a decode succeeded just because the result is truthy; check
json_last_error()or useJSON_THROW_ON_ERROR. - Implement
JsonSerializablewhen you need precise control over how an object is represented in JSON.
