PHP JSON

Su Subhalakshmi Chandran Updated 17 Oct 2026
7 min read ·Lesson 80 of 109
In this post

What Is JSON? In simple terms: JSON (JavaScript Object Notation) is a text format for exchanging data between programs. Web APIs, JavaScript front ends, mobile apps, and configuration files all use it

What Is JSON?

In simple terms: JSON (JavaScript Object Notation) is a text format for exchanging data between programs. Web APIs, JavaScript front ends, mobile apps, and configuration files all use it. PHP can turn its arrays and objects into JSON text and read JSON text back into PHP values.

{"id": 501, "customer": "Asha", "paid": true, "items": ["Pen", "Notebook"]}

Technically: JSON supports objects ({}), arrays ([]), strings, numbers, booleans (true/false), and null. PHP's JSON extension (always available since PHP 8.0) provides json_encode() to produce JSON and json_decode() to parse it, plus json_validate() (PHP 8.3+) to check JSON without decoding it.

How PHP Values Map to JSON

PHPJSON
int, floatnumber
stringstring
true / falsetrue / false
nullnull
List array ([0 => 'a', 1 => 'b'])array ["a","b"]
Associative array or list with gapsobject {"key": ...}
Objectobject (public properties, or jsonSerialize() output)

Encoding: json_encode()

Example

<?php
$order = [
    'id' => 501,
    'customer' => 'Asha',
    'paid' => true,
    'coupon' => null,
    'items' => ['Pen', 'Notebook'],
    'total' => 1250.5,
];

echo json_encode($order) . "\n";

Output

{"id":501,"customer":"Asha","paid":true,"coupon":null,"items":["Pen","Notebook"],"total":1250.5}
  • The associative array becomes a JSON object; the list items becomes a JSON array.
  • Types are preserved: numbers stay numbers, true stays true, and null stays null.

Common Flags

<?php
$data = ['name' => 'ஆஷா', 'url' => 'https://gocourse.in/php', 'price' => 1299.0];

echo json_encode($data) . "\n";
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION) . "\n";
echo json_encode(['a' => 1, 'b' => [1, 2]], JSON_PRETTY_PRINT) . "\n";

Output

{"name":"\u0b86\u0bb7\u0bbe","url":"https:\/\/gocourse.in\/php","price":1299}
{"name":"ஆஷா","url":"https://gocourse.in/php","price":1299.0}
{
    "a": 1,
    "b": [
        1,
        2
    ]
}
FlagEffect
JSON_UNESCAPED_UNICODEWrites non-ASCII characters (Tamil, Hindi, emoji) directly instead of \uXXXX escapes
JSON_UNESCAPED_SLASHESWrites / instead of \/
JSON_PRESERVE_ZERO_FRACTIONKeeps 1299.0 as a float (1299.0) instead of 1299
JSON_PRETTY_PRINTHuman-readable indentation (for files and debugging, not for API responses)
JSON_THROW_ON_ERRORThrows JsonException on errors (recommended)
JSON_FORCE_OBJECTEncodes every array as an object
JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_HEX_QUOTEscapes <, >, &, ', " — for embedding JSON in HTML (see Escaping Output)
JSON_NUMERIC_CHECKConverts numeric strings to numbers — use with care: phone numbers or PIN codes like "060042" lose their leading zero

Both escaped and unescaped forms are valid JSON; any JSON parser decodes \u0b86 back to ஆ.


Decoding: json_decode()

Syntax

json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed

Objects vs Associative Arrays

<?php
$json = '{"id": 7, "name": "Asha", "tags": ["php", "mysql"]}';

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

echo $asObject->name . "\n";
echo $asArray['tags'][1] . "\n";
var_dump($asObject instanceof stdClass);

Output

Asha
mysql
bool(true)
  • Without the second argument, JSON objects become stdClass objects (accessed with ->).
  • With true, they become associative arrays (accessed with []), which work with all array functions. Most applications use true.

Handling Invalid JSON

<?php
$broken = '{"id": 7,}';

var_dump(json_decode($broken, true));
echo json_last_error_msg() . "\n";

try {
    json_decode($broken, true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo get_class($e) . ": " . $e->getMessage() . "\n";
}

Output

NULL
Syntax error
JsonException: Syntax error

Why JSON_THROW_ON_ERROR Matters

Without the flag, invalid JSON returns null — which is also the correct result for the valid JSON text null. Checking json_last_error() after every call is easy to forget. With JSON_THROW_ON_ERROR, invalid input throws an exception that cannot be silently ignored. Use it for both json_encode() and json_decode().

Validating Without Decoding (PHP 8.3+)

<?php
var_dump(json_validate('{"id": 7}'));
var_dump(json_validate('{id: 7}'));

Output

bool(true)
bool(false)

json_validate() checks syntax using less memory than decoding — useful when you only need to know whether a large payload is valid JSON (for example, before storing it).


Pitfalls

Empty Arrays vs Empty Objects

<?php
echo json_encode(['tags' => [], 'meta' => []]) . "\n";
echo json_encode(['tags' => [], 'meta' => new stdClass()]) . "\n";

Output

{"tags":[],"meta":[]}
{"tags":[],"meta":{}}

An empty PHP array is encoded as an empty JSON array. If a client expects an object for a field (such as a settings map), use new stdClass() or (object) [] for empty values.

Lists With Gaps Become Objects

After unset() or array_filter(), a list may have gaps in its keys and is then encoded as an object ({"0":"a","2":"c"}). Use array_values() before encoding lists (see Indexed Arrays).

Large Integers

<?php
$json = '{"order_id": 12345678901234567890}';

var_dump(json_decode($json, true)['order_id']);
var_dump(json_decode($json, true, flags: JSON_BIGINT_AS_STRING)['order_id']);

Output

float(1.2345678901234567E+19)
string(20) "12345678901234567890"

Integers larger than PHP_INT_MAX are converted to floats and lose precision. JSON_BIGINT_AS_STRING keeps them exact as strings. Many APIs send large IDs as strings for this reason.

Invalid UTF-8

json_encode() fails if a string contains invalid UTF-8 (for example, data from a misconfigured legacy database). JSON_INVALID_UTF8_SUBSTITUTE replaces invalid bytes with � instead of failing.


Controlling Object Output: JsonSerializable

By default, json_encode() outputs an object's public properties. Implementing JsonSerializable lets a class decide exactly what its JSON looks like:

<?php
final class Product implements JsonSerializable
{
    public function __construct(
        private int $id,
        private string $name,
        private int $pricePaise,
        private DateTimeImmutable $addedAt,
        private string $supplierCost = 'internal',
    ) {}

    public function jsonSerialize(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->pricePaise / 100,
            'added_at' => $this->addedAt->format(DATE_ATOM),
        ];
    }
}

$product = new Product(7, 'Desk Lamp', 129900, new DateTimeImmutable('2026-10-15 09:30:00+05:30'));

echo json_encode($product, JSON_THROW_ON_ERROR) . "\n";

Output

{"id":7,"name":"Desk Lamp","price":1299,"added_at":"2026-10-15T09:30:00+05:30"}
  • Private properties are included only through jsonSerialize().
  • Internal data (supplierCost) is not exposed.
  • Values can be transformed for the API: the price is stored in paise but sent in rupees, and the date is formatted as ISO 8601.

Practical Example: A JSON API Endpoint

<?php
// api/products.php
declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');

function respond(int $status, array $body): never
{
    http_response_code($status);
    echo json_encode($body, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';

if ($method === 'GET') {
    respond(200, ['data' => [
        ['id' => 1, 'name' => 'Gel Pen', 'price' => 20],
        ['id' => 2, 'name' => 'Notebook', 'price' => 60],
    ]]);
}

if ($method === 'POST') {
    try {
        $input = json_decode(file_get_contents('php://input'), true, 32, JSON_THROW_ON_ERROR);
    } catch (JsonException) {
        respond(400, ['error' => 'Request body must be valid JSON.']);
    }

    $name = is_array($input) && is_string($input['name'] ?? null) ? trim($input['name']) : '';
    $price = is_array($input) ? filter_var($input['price'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) : false;

    if ($name === '' || $price === false) {
        respond(422, ['error' => 'Fields "name" (string) and "price" (positive integer) are required.']);
    }

    respond(201, ['data' => ['id' => 3, 'name' => $name, 'price' => $price]]);
}

respond(405, ['error' => 'Method not allowed.']);

Testing With curl

curl -X POST -H "Content-Type: application/json" \
     -d '{"name": "Desk Lamp", "price": 1299}' \
     http://localhost:8000/api/products.php

Response (status 201)

{"data":{"id":3,"name":"Desk Lamp","price":1299}}

Step-by-Step Explanation

  1. The Content-Type: application/json header tells clients how to interpret the response.
  2. respond() sets the status code, encodes the body, and stops the script; the never return type documents that it does not return.
  3. The request body is read from php://input (JSON is not available in $_POST) and decoded with a depth limit of 32, which protects against deeply nested malicious input.
  4. Invalid JSON returns 400 Bad Request; valid JSON with invalid fields returns 422 Unprocessable Content; a successful creation returns 201 Created.
  5. catch (JsonException) without a variable (PHP 8.0+) is used because the exception details are not needed.

Building complete APIs is covered in Project: REST API with PHP.


Common Mistakes

Not Checking for Errors

Effect: Invalid JSON silently becomes null.

Fix: Use JSON_THROW_ON_ERROR.

Forgetting true in json_decode()

Effect: You get stdClass objects, and $data['key'] fails.

Fix: json_decode($json, true).

Encoding Filtered Lists Without Re-Indexing

Effect: Clients receive objects instead of arrays.

Fix: array_values() before encoding.

Using JSON_NUMERIC_CHECK Blindly

Effect: Phone numbers, PIN codes, and IDs with leading zeros are corrupted.

Fix: Cast specific fields explicitly instead.

Echoing JSON Into HTML Without Escaping Flags

Effect: </script> inside a value can break out of a script block.

Fix: Use JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT when embedding JSON in HTML.


Best Practices

  • Always use JSON_THROW_ON_ERROR.
  • Decode to associative arrays and validate the structure before using the data.
  • Use JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES for readable output.
  • Control object output with JsonSerializable, and never expose internal fields.
  • Send correct Content-Type headers and HTTP status codes in JSON APIs.

Official reference: https://www.php.net/manual/en/book.json.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators
Ask about this post
AI Ask about this post

Ask questions about PHP JSON and get answers drawn from it.

Signed-in readers only.