Lesson 89 of 100
JSON15 min5 checksPHP 8.5

Handle JSON Failures Explicitly with JSON_THROW_ON_ERROR

Learn why json_decode() returning null is ambiguous, how JSON_THROW_ON_ERROR fixes it, and how to define a clear return value for malformed input.

01 · THE IDEAREAD → TRY → LEARN

The problem with a quiet null

By default, json_decode() reports failure by returning null. That would be fine if null could never be a successful result, but the JSON text null is perfectly valid and decodes to null too. When a function returns the same value for "the input said null" and "the input was broken", callers cannot tell the two apart without extra work.

The first example shows both cases. After a failed decode you can ask json_last_error_msg() what went wrong, but that is a second step that is easy to forget.

Example
<?php
var_dump(json_decode('null'));
var_dump(json_decode('{bad'));
echo json_last_error_msg(), PHP_EOL;
Output
NULL
NULL
Syntax error

Both var_dump() calls print NULL. Only the error message reveals that the second input was malformed.

Throwing instead of returning null

The signature is json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed. The flags argument accepts JSON_THROW_ON_ERROR. With that flag, a decoding failure throws a JsonException instead of returning null. The exception message describes the problem and getCode() returns the matching JSON error code.

Example
<?php
try {
    json_decode('{"a":1,}', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo get_class($e), ': ', $e->getMessage(), ' (', $e->getCode(), ')', PHP_EOL;
}
Output
JsonException: Syntax error (4)

A trailing comma is not allowed in JSON, so the call throws. Because the failure is an exception, a successful null result is now unmistakable: if no exception was thrown, null came from the input.

Note that depth and flags are positional, so to reach flags you must also pass associative and depth. Using true and 512 keeps the defaults you would normally want for arrays. Exceeding the nesting depth is also reported as a JsonException when the flag is set.

Decide what malformed input means

Throwing is only half the design. A function that wraps decoding should state its contract. Common choices are:

  • let the JsonException propagate, so the caller decides;
  • return a caller-supplied default;
  • return an empty array when you only accept lists or objects.

The third example picks the last option. It also checks the type, because syntactically valid JSON such as 7 is not a list.

Example
<?php
function decodeList(string $json): array
{
    try {
        $value = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
    } catch (JsonException) {
        return [];
    }

    return is_array($value) ? $value : [];
}

echo json_encode([decodeList('[1,2]'), decodeList('[1,'), decodeList('7')]), PHP_EOL;
Output
[[1,2],[],[]]

The output is [[1,2],[],[]]: one good list, one truncated document, and one valid but wrongly typed value.

Edge cases to remember

  • An empty string is not valid JSON, so it counts as malformed input.
  • 0, false, [] and null are all valid documents. Never test the result with empty() or ?: to detect failure, or you will discard good data.
  • Catch JsonException specifically rather than a broad Throwable, so unrelated bugs stay visible.

Exercise

Write decodeOrDefault(string $json, mixed $default): mixed. It must decode the JSON text with associative arrays enabled and JSON_THROW_ON_ERROR. When decoding succeeds, return the decoded value exactly, even if it is null, 0, false or an empty array. When a JsonException is thrown, return $default. The tests compare the returned values, and they include a valid null document and an empty string, so a check like $result ?: $default will fail.

Recap

json_decode() signals failure with null by default, which collides with the valid JSON null. Pass JSON_THROW_ON_ERROR, catch JsonException, and return a value that your function's contract defines for bad input. Keep valid falsy results intact.

Before you start

Functions · Arrays · Types

You’ll practice
  • Use JSON_THROW_ON_ERROR and define malformed-input return values
  • Implement and test a deterministic PHP 8.4 function
Your task

Make decodeOrDefault() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.