PHP: How do you check whether an array is a list or associative?

PHP has only one array type, which serves as both a list and a dictionary. Sometimes you need to know which of the two you are holding — and by the time you get to JSON it definitely matters.

From PHP 8.1 onwards

Since PHP 8.1 there is a built-in function for this: array_is_list(). It returns true when the keys start at 0, are integers and increase without gaps:

['a','b','c']      => true
[0=>'a', 1=>'b']   => true
[1=>'a', 2=>'b']   => false     (does not start at 0)
[0=>'a', 2=>'b']   => false     (gap)
['x'=>1]           => false
[]                 => true      (the empty array counts as a list)

The last case is deliberate: an empty array can be regarded as an empty list without contradiction.

Before PHP 8.1

If you are on an older version, you will find this rule of thumb everywhere:

<?php
function isList(array $a): bool {
  return array_keys($a) === range(0, count($a) - 1);
}
?>

It works — except in one case. Let us compare it with the built-in function:

              old formula   array_is_list
['a','b']     true          true
[]            false         true          <- differs
[1=>'a']      false         false
['0'=>'a']    true          true

For the empty array the old formula returns false, so it considers it associative. The reason lies in range():

range(0, -1)   =>   [0, -1]

If the start value is greater than the end value, range() counts backwards. So for an empty array [] is compared with [0, -1], and those are, as you would expect, not the same.

If you still have this formula somewhere in your code, it is worth searching for it deliberately. If you have to stay on PHP 8.0 or older, an extra check helps:

<?php
function isList(array $a): bool {
  if ($a === []) {
    return true;
  }

  return array_keys($a) === range(0, count($a) - 1);
}
?>

Numeric keys as strings

A detail that often causes confusion: PHP automatically converts numeric string keys into integers.

['0'=>'x','1'=>'y']  keys : [0,1]
type of the first key     : int

So ['0' => 'a'] does not merely behave like a list, it is one. This conversion always happens, not just during comparison.

Why this matters at all

The distinction feels academic until you have overlooked it once at an interface. json_encode() produces different formats:

json_encode(['a','b','c'])   =>   ["a","b","c"]

An array. Now let us remove an element:

<?php
  $list = ['a', 'b', 'c'];
  unset($list[1]);
  echo json_encode($list);
?>
{"0":"a","2":"c"}

The array has become an object. And it has to, because a JSON array has no keys with which the gap could be expressed.

For the client of your interface, though, that is a change of format. Anyone expecting an array and suddenly receiving an object breaks — and only sometimes, namely exactly when an element was removed. Bugs like that take a long time to find.

The fix is an array_values() before the data leaves the building:

json_encode(array_values($list))   =>   ["a","c"]

Summary

  • From PHP 8.1: array_is_list().
  • The old range() formula answers the empty array wrongly.
  • Numeric string keys automatically become integers.
  • Call array_values() before json_encode() if the result should be a JSON array.

About Netcup (advertisement)

The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).