http clients and apis

JSON Encoding And Decoding

JSON encoding turns PHP values into a JSON string. JSON decoding turns a JSON string into PHP values.

APIs depend on this boundary. Your PHP code works with arrays, strings, integers, booleans, and nulls; the network carries bytes of JSON text.

Decode JSON into arrays

PHP example
<?php

declare(strict_types=1);

$json = '{"id":123,"name":"Notebook"}';
$product = json_decode($json, true, flags: JSON_THROW_ON_ERROR);

echo $product['name'] . PHP_EOL;
echo json_encode(['ok' => true], JSON_THROW_ON_ERROR) . PHP_EOL;

// Prints:
// Notebook
// {"ok":true}

The second argument true tells PHP to decode JSON objects as associative arrays. Without it, JSON objects become stdClass objects.

For API code, arrays are often simpler because validation and transformation code can use normal array access.

Use exceptions for JSON errors

Always prefer JSON_THROW_ON_ERROR in application code. Without it, invalid JSON may silently become null, which can be confused with valid JSON null.

PHP example
<?php

declare(strict_types=1);

try {
    json_decode('{"broken":', true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
    echo 'Invalid JSON' . PHP_EOL;
}

// Prints:
// Invalid JSON

Lesson 8 goes deeper into JSON errors; the important habit starts here.

Validate the decoded shape

Decoding JSON only proves the text was valid JSON. It does not prove the fields are present or the types are correct.

PHP example
<?php

declare(strict_types=1);

function productNameFromJson(string $json): string
{
    $data = json_decode($json, true, flags: JSON_THROW_ON_ERROR);

    if (!is_array($data) || !isset($data['name']) || !is_string($data['name'])) {
        throw new InvalidArgumentException('Product name is missing.');
    }

    return $data['name'];
}

echo productNameFromJson('{"name":"Notebook"}') . PHP_EOL;

// Prints:
// Notebook

This kind of shape check prevents warnings and bad assumptions later in the code.

Encode PHP values deliberately

PHP example
<?php

declare(strict_types=1);

$response = [
    'id' => 123,
    'active' => true,
    'tags' => ['stationery', 'paper'],
    'discount' => null,
];

echo json_encode($response, JSON_THROW_ON_ERROR) . PHP_EOL;

// Prints:
// {"id":123,"active":true,"tags":["stationery","paper"],"discount":null}

Associative arrays encode as JSON objects. Sequential arrays encode as JSON arrays.

Empty arrays and empty objects

An empty PHP array encodes as [], not {}.

PHP example
<?php

declare(strict_types=1);

echo json_encode([], JSON_THROW_ON_ERROR) . PHP_EOL;
echo json_encode((object) [], JSON_THROW_ON_ERROR) . PHP_EOL;

// Prints:
// []
// {}

This matters when an API contract expects an empty object. Do not guess; check the contract.

Numbers can surprise you

JSON has numbers, but PHP has integers and floats. Large IDs from other systems may not fit safely into every language's number type. Many APIs send large identifiers as strings for that reason.

Do not cast IDs to integers unless the API contract says they are numeric IDs within a safe range.

UTF-8 matters

JSON strings must be valid UTF-8. If you read data from old files, external feeds, or legacy databases, encoding problems can make json_encode() fail.

Use JSON_THROW_ON_ERROR so that failure is visible.

What to check in a project

Check that json_decode() uses JSON_THROW_ON_ERROR.

Check whether decoded objects should be arrays or stdClass, and keep the style consistent.

Check the decoded shape before trusting fields.

Check empty arrays versus empty objects when matching an external contract.

Check large identifiers and avoid unnecessary numeric casts.

What you should be able to do

After this lesson, you should be able to encode and decode JSON safely, use exceptions for JSON errors, validate decoded data before using it, and recognise common API contract issues around arrays, objects, nulls, UTF-8, and numeric IDs.

Practice

Task: Decode And Validate JSON

Write a small PHP script that decodes product JSON, validates the shape, and encodes a response.

Requirements

  • Use declare(strict_types=1);.
  • Decode JSON with JSON_THROW_ON_ERROR.
  • Require id and name.
  • Treat id as an integer and name as a string.
  • Include one valid JSON case.
  • Include one invalid shape case.
  • Encode a success response with json_encode(..., JSON_THROW_ON_ERROR).

Check Your Work

Run the script and confirm the invalid shape is rejected even though it is valid JSON.

Show solution

This solution separates JSON syntax validity from application shape validity.

PHP example
<?php

declare(strict_types=1);

/**
 * @return array{id: int, name: string}
 */
function decodeProduct(string $json): array
{
    $data = json_decode($json, true, flags: JSON_THROW_ON_ERROR);

    if (
        !is_array($data)
        || !isset($data['id'], $data['name'])
        || !is_int($data['id'])
        || !is_string($data['name'])
    ) {
        throw new InvalidArgumentException('Product JSON has the wrong shape.');
    }

    return ['id' => $data['id'], 'name' => $data['name']];
}

$product = decodeProduct('{"id":123,"name":"Notebook"}');

echo json_encode(['data' => $product], JSON_THROW_ON_ERROR) . PHP_EOL;

try {
    decodeProduct('{"id":"123","title":"Notebook"}');
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// {"data":{"id":123,"name":"Notebook"}}
// Product JSON has the wrong shape.

Why This Works

The valid case proves decoding and encoding work. The invalid case proves valid JSON can still be rejected when it does not match the API contract.