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
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
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
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
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
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
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
customerEmailto be a non-empty string. - Require
itemsto be a non-empty array. - Return a small response array containing
acceptedanditemCount. - 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
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.