data types and standard library

JSON

JSON is the common format for HTTP APIs, webhooks, queues, browser requests, configuration files, and third-party service payloads.

In PHP, JSON work has two separate steps: decode the JSON string into PHP data, then validate that the decoded data has the shape your code expects. Successful decoding only proves the text was valid JSON. It does not prove the payload is safe or useful.

Decode with exceptions

Use JSON_THROW_ON_ERROR so invalid JSON becomes an exception instead of a silent null.

PHP example
<?php

declare(strict_types=1);

$rawJson = '{"items":["Notebook","Pen"]}';
$payload = json_decode($rawJson, true, flags: JSON_THROW_ON_ERROR);

echo $payload['items'][0] . PHP_EOL;

// Prints:
// Notebook

The second argument, true, tells PHP to decode objects as associative arrays. That is usually the easiest shape to validate in application code.

Validate the decoded shape

Do not assume keys exist just because decoding succeeded.

PHP example
<?php

declare(strict_types=1);

function itemCountFromJson(string $rawJson): int
{
    $payload = json_decode($rawJson, true, flags: JSON_THROW_ON_ERROR);

    if (!is_array($payload)) {
        throw new InvalidArgumentException('Payload must be a JSON object.');
    }

    if (!isset($payload['items']) || !is_array($payload['items'])) {
        throw new InvalidArgumentException('Payload must contain an items array.');
    }

    return count($payload['items']);
}

echo itemCountFromJson('{"items":["Notebook","Pen"]}') . PHP_EOL;

// Prints:
// 2

This is the boundary where junior developers often skip a step. JSON can be valid while still being the wrong type, missing required fields, or containing unexpected values.

Encode response data

Use json_encode() for outgoing JSON and throw on errors there too.

PHP example
<?php

declare(strict_types=1);

$response = [
    'ok' => true,
    'itemCount' => 2,
];

echo json_encode($response, JSON_THROW_ON_ERROR) . PHP_EOL;

// Prints:
// {"ok":true,"itemCount":2}

In a web controller, you would also set the Content-Type: application/json header and the correct HTTP status code. The data shape and the transport details are both part of a reliable API response.

Handle invalid JSON separately from invalid data

Malformed JSON and valid-but-wrong JSON are different failures.

PHP example
<?php

declare(strict_types=1);

function decodeObject(string $rawJson): array
{
    try {
        $payload = json_decode($rawJson, true, flags: JSON_THROW_ON_ERROR);
    } catch (JsonException $exception) {
        throw new InvalidArgumentException('Request body is not valid JSON.', 0, $exception);
    }

    if (!is_array($payload)) {
        throw new InvalidArgumentException('Request body must be a JSON object.');
    }

    return $payload;
}

try {
    decodeObject('{"name":');
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// Request body is not valid JSON.

This distinction helps API clients fix the right problem and helps logs tell a clearer story.

Preserve large identifiers

Some systems send large numeric IDs that do not fit safely into normal integer handling across languages. If an ID is not used for maths, treat it as a string.

PHP example
<?php

declare(strict_types=1);

$rawJson = '{"externalId":9223372036854775808}';
$payload = json_decode($rawJson, true, flags: JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING);

echo gettype($payload['externalId']) . PHP_EOL;
echo $payload['externalId'] . PHP_EOL;

// Prints:
// string
// 9223372036854775808

IDs, account numbers, order references, and tracking numbers should usually be strings even when they contain only digits.

Use clear output options

Pretty printing is useful for logs, fixtures, and generated files. Compact JSON is usually better for API responses.

PHP example
<?php

declare(strict_types=1);

$payload = [
    'status' => 'accepted',
    'items' => ['Notebook', 'Pen'],
];

echo json_encode($payload, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) . PHP_EOL;

// Prints:
// {
//     "status": "accepted",
//     "items": [
//         "Notebook",
//         "Pen"
//     ]
// }

Choose options because the output is consumed somewhere specific, not because one format looks nicer in isolation.

What to remember

JSON is an application boundary. Decode with exceptions, validate the decoded shape, treat external IDs carefully, encode responses deliberately, and keep invalid JSON separate from invalid business data. A reliable JSON handler should be boring to review because every assumption is visible.

Practice

Task: Decode and validate an order request

Write a function that accepts a raw JSON request body for a simple order.

Requirements

  • Use declare(strict_types=1);.
  • Decode with JSON_THROW_ON_ERROR.
  • Require the decoded value to be a JSON object.
  • Require customerEmail to be a non-empty string.
  • Require items to be a non-empty array.
  • Return a small response array containing accepted and itemCount.
  • Encode and print the response JSON.
  • Show one invalid JSON case or invalid data case by catching the exception.
  • Include the expected output as comments in the same PHP code block.

The task should prove both parts of JSON handling: decoding text and validating the data shape.

Show solution
PHP example
<?php

declare(strict_types=1);

function acceptOrderRequest(string $rawJson): array
{
    try {
        $payload = json_decode($rawJson, true, flags: JSON_THROW_ON_ERROR);
    } catch (JsonException $exception) {
        throw new InvalidArgumentException('Request body is not valid JSON.', 0, $exception);
    }

    if (!is_array($payload)) {
        throw new InvalidArgumentException('Request body must be a JSON object.');
    }

    if (!isset($payload['customerEmail']) || !is_string($payload['customerEmail']) || trim($payload['customerEmail']) === '') {
        throw new InvalidArgumentException('Customer email is required.');
    }

    if (!isset($payload['items']) || !is_array($payload['items']) || $payload['items'] === []) {
        throw new InvalidArgumentException('At least one item is required.');
    }

    return [
        'accepted' => true,
        'itemCount' => count($payload['items']),
    ];
}

$response = acceptOrderRequest('{"customerEmail":"nia@example.com","items":["Notebook","Pen"]}');

echo json_encode($response, JSON_THROW_ON_ERROR) . PHP_EOL;

try {
    acceptOrderRequest('{"customerEmail":"","items":[]}');
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// {"accepted":true,"itemCount":2}
// Customer email is required.

The function treats JSON parsing and payload validation as separate responsibilities. That makes it clear whether a request failed because the body was not JSON or because the JSON did not contain the fields the application needs.