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

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)

Pro Tips

📋 Quick Reference — JSON

SyntaxExampleWhat It Does
json_encode()json_encode($arr)PHP value → JSON string
JSON_PRETTY_PRINTjson_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_ERRORjson_decode($s, true, 512, JSON_THROW_ON_ERROR)Throw on bad JSON
json_last_error_msg()json_last_error_msg()Last error as text
JsonSerializablejsonSerialize(): arrayCustomise 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!

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

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.

Related lessons