← All writing

Missing, null, and zero: updating API fields without erasing good data

PHP 8.3+Sources checked 2026-09-12

This import contract preserves missing/unknown values and accepts a real zero.

An inventory API sends {"stock":0}. That is a valid stock count. Another response omits stock because the supplier did not include that field. Treating both responses as "empty" makes a synchronization job unreliable.

Write the update contract first

For this example, missing means leave the previous value alone. Null means the supplier cannot provide a value, so also preserve the previous count. An integer zero means replace the count with zero. Negative values and strings are invalid.

<?php
function nextStock(array $payload, int $previous): int
{
    if (!array_key_exists('stock', $payload)
        || $payload['stock'] === null) {
        return $previous;
    }
    if (!is_int($payload['stock']) || $payload['stock'] < 0) {
        throw new InvalidArgumentException('Invalid stock count');
    }
    return $payload['stock'];
}
assert(nextStock([], 12) === 12);
assert(nextStock(['stock' => null], 12) === 12);
assert(nextStock(['stock' => 0], 12) === 0);
assert(nextStock(['stock' => 7], 12) === 7);
echo "four update cases passed", PHP_EOL;

The explicit type check is intentional. If the supplier documents numeric strings, introduce a separate conversion step with range checks. Do not accept arbitrary coercion just because a few fixtures happen to work.

Why empty is the wrong question

The business question is whether a field is present and valid. A truthiness check answers something else. In the zero-stock case it can preserve twelve units that the supplier says no longer exist. That can flow into an incorrect availability display.

For a nullable profile biography, your contract may instead define null as an intentional deletion. Reuse the decision process, not this stock-specific behavior.

Add tests for negative integers, numeric strings, arrays, and very large numbers. Record invalid responses without storing secrets or the whole upstream payload. The PHP array_key_exists reference explains its distinction from isset for null values.