← All writing

Decode JSON in PHP without confusing malformed input with null

PHP 8.3+Sources checked 2026-09-12

A valid HTTP response is not yet a valid business record.

A successful HTTP request does not guarantee a usable JSON document. A proxy can return HTML, a supplier can truncate a response, and the literal JSON value null can be perfectly valid. Your importer needs to distinguish those cases before touching stored data.

Separate syntax from shape

<?php
function decodeProduct(string $body): array
{
    $value = json_decode($body, false, 64, JSON_THROW_ON_ERROR);
    if (!$value instanceof stdClass) {
        throw new UnexpectedValueException('Expected an object');
    }
    if (!property_exists($value, 'sku') || !is_string($value->sku)) {
        throw new UnexpectedValueException('Missing string SKU');
    }
    return ['sku' => $value->sku];
}
assert(decodeProduct('{"sku":"AX-204"}') === ['sku' => 'AX-204']);
try {
    decodeProduct('{"sku":');
    throw new RuntimeException('Expected malformed JSON to fail');
} catch (JsonException) {
    echo "syntax rejected", PHP_EOL;
}
try {
    decodeProduct('null');
    throw new RuntimeException('Expected wrong shape to fail');
} catch (UnexpectedValueException) {
    echo "shape rejected", PHP_EOL;
}

Decoding into an object preserves the distinction between a JSON object and a list, including empty ones. The returned array contains only the field this tiny example promises.

Keep failures outside the write transaction

Fetch and decode first. Then validate the product contract. Only a valid, normalized record should enter the database update. A parser failure should never become an empty successful result that erases the previous product.

The depth of 64 is a deliberate limit for this flat example. Request-size and response-size limits are separate concerns; a shallow document can still contain an enormous string.

For identifiers larger than PHP's integer range, prefer string identifiers in the API contract. If you receive numeric JSON identifiers, investigate JSON_BIGINT_AS_STRING rather than casting an already-rounded number afterward.

Test HTML, malformed JSON, null, a list, and an object with the wrong SKU type. These are independent failure modes. PHP's JSON decoder reference documents exception flags and large-integer handling.