Working with JSON
Reviewed & published by Brayan K
JSON is the universal language of web APIs. PHP gives you exactly two functions for it — json_encode to send data out and json_decode to read it back in. Master those two, plus error handling, and you can talk to any API on the internet.
Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- Encode PHP arrays and objects to JSON strings
- Pretty-print and control output with encode flags
- Decode JSON into objects or associative arrays
- Detect and handle invalid JSON safely
- Throw and catch JsonException on bad input
- Round-trip an object: PHP → JSON → PHP for an API
1️⃣ Encoding to JSON
json_encode($value) serialises a PHP value into a JSON string. An associative array becomes a JSON object { }, a list array becomes a JSON array [ ], and scalars map directly. Pass the JSON_PRETTY_PRINT flag for nicely indented output when a human needs to read it.
<?php
// json_encode turns a PHP value INTO a JSON string.
// Great for sending data to an API or a JavaScript front end.
$user = [
'name' => 'Ada',
'age' => 36,
'active' => true,
'roles' => ['admin', 'editor'],
];
echo json_encode($user) . "\n";
// JSON_PRETTY_PRINT formats it with indentation for humans.
echo json_encode($user, JSON_PRETTY_PRINT) . "\n";
?>2️⃣ Decoding from JSON
json_decode($string) goes the other way. With one argument you get a stdClass object (access with ->); pass true as the second argument to get an associative array instead (access with []). Most PHP code uses the array form — it's easier to loop and pass around.
<?php
// json_decode turns a JSON string back INTO a PHP value.
$json = '{"name":"Ada","age":36,"roles":["admin","editor"]}';
// By default you get an stdClass OBJECT — access with ->.
$obj = json_decode($json);
echo $obj->name . "\n"; // Ada
echo $obj->roles[0] . "\n"; // admin
// Pass true as the 2nd arg to get an associative ARRAY instead — access with [].
$arr = json_decode($json, true);
echo $arr['name'] . "\n"; // Ada
echo $arr['roles'][1] . "\n"; // editor
echo gettype($obj) . ' vs ' . gettype($arr) . "\n"; // object vs array
?>3️⃣ Handling Invalid JSON
Here's a trap: when JSON is malformed, json_decode returns null silently — and null is also a valid JSON value, so a bare null-check is ambiguous. Either inspect json_last_error(), or — much cleaner on modern PHP — pass JSON_THROW_ON_ERROR so bad input throws a JsonException you can catch.
<?php
// Bad JSON returns null from json_decode — silently! Always check.
$bad = '{"name": "Ada", }'; // trailing comma = invalid JSON
$data = json_decode($bad, true);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
echo "Decode failed: " . json_last_error_msg() . "\n";
}
// The modern, cleaner way: ask json_decode to THROW on error.
try {
json_decode($bad, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
?>4️⃣ The Round-Trip
Almost every API endpoint is a round-trip: take a PHP object, json_encode it for the response, then on the next request json_decode the body back into PHP. Note that json_encode serialises only an object's public properties — private and protected ones are skipped (you'd implement JsonSerializable to customise that).
<?php
// A round-trip: PHP object -> JSON -> PHP, the backbone of every API.
class Product {
public function __construct(
public string $name,
public float $price,
public array $tags,
) {}
}
$p = new Product('Coffee Mug', 12.50, ['kitchen', 'gift']);
// Public properties serialise automatically.
$json = json_encode($p, JSON_PRETTY_PRINT);
echo $json . "\n";
// Decode back into a usable array.
$back = json_decode($json, true);
echo "Reloaded: " . $back['name'] . ' costs $' . $back['price'] . "\n";
?>Now you try — fill in each ___ using the 👉 hint, then run it and check against the Output panel.
<?php
// 🎯 YOUR TURN — fill in each blank marked ___ , then run it.
$cart = ['item' => 'Book', 'qty' => 2];
// 1) Turn the array into a JSON string:
$json = ___($cart); // 👉 the function that PHP value -> JSON
echo $json . "\n"; // should print: {"item":"Book","qty":2}
// 2) Decode JSON into an ASSOCIATIVE ARRAY (not an object):
$data = json_decode('{"item":"Pen","qty":5}', ___); // 👉 the 2nd arg for arrays
echo $data['qty'] . "\n"; // should print: 5
// ✅ Expected output:
// {"item":"Book","qty":2}
// 5
?>🧩 Reorder Challenge
These lines round-trip a value through JSON — but they're scrambled. Put them in the order that prints Ada.
B → D → C → A
Build the array (B), encode it to a JSON string (D), decode that string back into an array (C), then read the value out (A). Each step depends on the variable produced by the one before it.
🧠 Quick Recall — Predict the Output
{"a":1,"b":true} — string keys produce a JSON object, and the PHP true becomes JSON true.
2. What does gettype() report?
object — without the true flag, json_decode returns a stdClass object. You'd read $x->n, not $x['n'].
3. What is $y after this?
NULL — invalid JSON makes json_decode return null silently. That's why you check json_last_error() or use JSON_THROW_ON_ERROR.
Common Errors (and the fix)
- "Trying to access array offset on value of type null" — json_decode returned null on bad JSON. Check the error before using the result.
- You used -> but got an "array" error (or vice versa) — you passed true (array) but accessed with ->. Match the access style to the decode mode.
- A private property is missing from your JSON — json_encode only includes public properties. Implement JsonSerializable to control output.
- Accented characters show as é — that's valid JSON, but add JSON_UNESCAPED_UNICODE if you want them literal.
Pro Tips
- 💡 Always decode with true unless you specifically want an object — arrays are easier to work with in PHP.
- 💡 Add JSON_THROW_ON_ERROR so bad input fails loudly with an exception instead of a silent null.
- 💡 Combine flags with |: JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE is a great default for API responses.
📋 Quick Reference — JSON
| Syntax | Example | What It Does |
|---|---|---|
| json_encode() | json_encode($arr) | PHP value → JSON string |
| JSON_PRETTY_PRINT | json_encode($a, JSON_PRETTY_PRINT) | Indented output |
| json_decode() | json_decode($s) | JSON → stdClass object |
| json_decode($s, true) | json_decode($s, true) | JSON → associative array |
| JSON_THROW_ON_ERROR | json_decode($s, true, 512, JSON_THROW_ON_ERROR) | Throw on bad JSON |
| json_last_error_msg() | json_last_error_msg() | Last error as text |
| JsonSerializable | jsonSerialize(): array | Customise encode output |
Mini-Challenge: API Response
No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments.
<?php
// 🎯 MINI-CHALLENGE: an API-style response.
// No code is filled in — work from the steps below, then run it.
//
// 1. Build an associative array $response with:
// 'status' => 'ok'
// 'count' => 2
// 'results' => [['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B']]
// 2. Encode it with json_encode(..., JSON_PRETTY_PRINT) and echo it.
// 3. Decode your own JSON back with the true flag, then echo:
// First result: A (reach into ['results'][0]['name'])
//
// ✅ Expected output (pretty JSON), then:
// First result: A
// your code here
?>🎉 Lesson Complete!
- ✅ json_encode turns PHP values into JSON; JSON_PRETTY_PRINT formats them
- ✅ json_decode($s) gives an object; add true for an array
- ✅ Bad JSON returns null silently — check the error or use JSON_THROW_ON_ERROR
- ✅ Only public properties serialise; use JsonSerializable to customise
- ✅ The PHP → JSON → PHP round-trip is the heart of every API
Practice quiz
What does json_decode return by default (with no second argument)?
- An associative array
- A string
- A stdClass object
- null
Answer: A stdClass object. By default json_decode returns a stdClass object, so you read properties with the arrow: $data->name. Pass true for an array.
What does json_decode($json, true) return?
- An associative array
- A stdClass object
- A JSON string
- false
Answer: An associative array. Passing true as the second argument gives an associative array, read with brackets: $data['name'].
What does echo json_encode(['a' => 1, 'b' => true]); print?
- {"a":1,"b":1}
- [1,true]
- {"a":"1","b":"true"}
- {"a":1,"b":true}
Answer: {"a":1,"b":true}. String keys produce a JSON object, and the PHP boolean true becomes JSON true (not 1).
What does json_decode return when given invalid JSON?
- false
- null (silently)
- An empty array
- It throws by default
Answer: null (silently). Invalid JSON makes json_decode return null silently — which is why you check json_last_error() or use JSON_THROW_ON_ERROR.
Which flag makes json_decode throw a JsonException on bad input instead of returning null?
- JSON_THROW_ON_ERROR
- JSON_PRETTY_PRINT
- JSON_UNESCAPED_UNICODE
- JSON_ERROR_NONE
Answer: JSON_THROW_ON_ERROR. JSON_THROW_ON_ERROR makes malformed input throw a JsonException you can catch, rather than silently returning null.
Which object properties does json_encode serialise by default?
- All properties
- Only private properties
- Only public properties
- Only properties listed in a __toString
Answer: Only public properties. json_encode serialises only public properties; private and protected ones are skipped. Implement JsonSerializable to customise.
Which flag makes json_encode output indented, human-readable JSON?
- JSON_INDENT
- JSON_PRETTY_PRINT
- JSON_FORMAT
- JSON_HUMAN
Answer: JSON_PRETTY_PRINT. JSON_PRETTY_PRINT formats the output with indentation for readability.
How do you combine multiple json_encode flags?
- With commas
- With +
- You can only pass one flag
- With the bitwise OR operator |
Answer: With the bitwise OR operator |. Flags combine with bitwise OR, e.g. json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE).
Why is a bare null check after json_decode ambiguous?
- null is never returned
- The literal JSON value null also decodes to null
- json_decode returns false on error
- null means success
Answer: The literal JSON value null also decodes to null. json_decode returns null both for the literal JSON null and for invalid JSON, so use json_last_error() to tell them apart.
Which interface lets you customise exactly what an object encodes to?
- Serializable
- Stringable
- JsonSerializable
- ArrayAccess
Answer: JsonSerializable. Implement JsonSerializable and define jsonSerialize() to return the exact array or value you want encoded.
Continue this course
- Previous: Dates & Times (DateTime)
- Next: Checkpoint: Data & Types — Combine enums, match, closures and JSON in a build challenge — then a quiz
- Quick reference: PHP cheat sheet
- From the blog: Building Your First REST API with PHP
Frequently asked questions
Does json_decode return an array or an object?
By default json_decode returns a stdClass object, so you read properties with the arrow: $data->name. Pass true as the second argument and you get an associative array instead, read with brackets: $data['name']. Most PHP developers prefer the array form because it's easier to loop over and pass around, so json_decode($json, true) is the idiomatic default. Pick one style and stay consistent within a project.
Why does json_decode return null and how do I detect a real error?
json_decode returns null both for the literal JSON null and for invalid JSON, which makes a bare null check ambiguous. The robust check is json_last_error() !== JSON_ERROR_NONE (with json_last_error_msg() for the text). Even cleaner on modern PHP: pass the JSON_THROW_ON_ERROR flag so a malformed string throws a JsonException you can catch, instead of silently returning null. Always validate decoded input — it usually comes from outside your app and can't be trusted.
How do I make json_encode output readable, and handle special characters?
Pass the JSON_PRETTY_PRINT flag for indented, human-friendly output. By default PHP escapes forward slashes and non-ASCII characters (so é becomes \u00e9); add JSON_UNESCAPED_SLASHES and JSON_UNESCAPED_UNICODE to keep URLs and accented text literal. Flags combine with the bitwise OR: json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE). For API responses sent to a browser, those three flags together usually give the cleanest result.
How does json_encode handle my objects and their private properties?
json_encode serialises only the public properties of an object by default; private and protected properties are skipped. To control exactly what gets serialised, implement the JsonSerializable interface and define a jsonSerialize() method that returns the array (or value) you want encoded. This is the clean way to expose a curated shape — for example renaming fields, hiding a password, or formatting a date — without exposing your internal property layout.