PHPのarray_filterはキーを保持する json_encodeで配列がオブジェクトになる理由と”0″が消える落とし穴

array_filter()は、コールバックがtrueを返した要素だけを残す関数です。元のキーをそのまま保持するため、要素が抜けると添字が歯抜けになり、json_encode()の結果が配列ではなくオブジェクトに化けることがあります。

「空の値を消したいだけ」のつもりで書いた1行が、フロントエンドに渡したJSONの形を変えてしまう。地味ですが、ハマると原因に気づきにくいやつです。私も、画面側のループが急に動かなくなって、API側のarray_filter()にたどり着いた側です。

array_filterの結果はなぜ歯抜けの配列になる?

array_filter()は連想配列にも使える関数なので、キーを振り直しません。リストとして使っていた配列でも、それは同じです。

<?php
$tags = ['php', '', 'sql'];
$filtered = array_filter($tags);

print_r($filtered);
// Array ( [0] => php [2] => sql )

echo json_encode($filtered);
// {"0":"php","2":"sql"}

json_encode()は、キーが0から連番で並んでいる配列だけをJSONの配列にします。1つ抜けて[0, 2]になった時点で、連想配列扱いになりオブジェクトとして出力されます。

リストに戻すにはarray_valuesで包む

リストとして扱いたいなら、array_filter()をarray_values()で包んでキーを振り直すのが定番です。キーを残す意味がある場面(IDがキーなど)では逆に包まない、という使い分けになります。

<?php
$tags = ['php', '', 'sql'];
$list = array_values(array_filter($tags));

echo json_encode($list);
// ["php","sql"]

これで、APIのレスポンスの形が入力に左右されなくなります。「リストを返す関数は、最後にarray_values()を通す」と決めておくと、あとで楽だと思います。

コールバックを省略すると”0″も消えるのはなぜ?

コールバックを省略したarray_filter()は、真偽値への変換でfalseになる要素をまとめて取り除きます。空文字やnullだけでなく、0、0.0、文字列の”0″、false、空配列もすべて消えます。

<?php
$values = ['a', '', '0', 0, null, false, [], 'b'];
print_r(array_filter($values));
// Array ( [0] => a [7] => b )

「空欄を除きたい」だけのフォーム入力で、数量の”0″や、値引き額の0円まで一緒に消えてしまう、というのが典型的な事故です。なお、文字列の”0.0″は真偽値ではtrueなので残ります。この辺りの非対称さが、またややこしいところです。

消したいものを明示するなら、コールバックで条件を書きます。

<?php
$values = ['a', '', '0', null, 'b'];
$result = array_filter($values, fn($v) => $v !== null && $v !== '');
print_r($result);
// Array ( [0] => a [2] => 0 [4] => b )

“0”が残ることを意図して書いた、と読み手に伝わるのも嬉しい点です。

キーも見て絞り込むにはどう書く?

第3引数にARRAY_FILTER_USE_KEYを渡すとコールバックにはキーだけが、ARRAY_FILTER_USE_BOTHを渡すと値とキーの両方が(この順で)渡されます。USE_BOTHでは引数の順序が「値、キー」である点に注意してください。

<?php
$ages = ['taro' => 20, 'hanako' => 15, 'jiro' => 30];

// 値とキーの両方を使う
$result = array_filter(
    $ages,
    fn($age, $name) => $age >= 18 && $name !== 'jiro',
    ARRAY_FILTER_USE_BOTH
);
print_r($result);
// Array ( [taro] => 20 )

連想配列からホワイトリストのキーだけ取り出す、といった処理にも使えます。ただし、キーだけで絞るならarray_intersect_key()のほうが意図が短く書けることも多いかもしれません。

まとめ

array_filter()は、キーを保持する、コールバックなしだと0や”0″も消える、という2点だけ覚えておけば、だいぶ事故が減る関数です。リストとして返すならarray_values()で包む、空だけ消したいなら条件を自分で書く。それだけで、JSONの形も数値の0も守れます。地味ですが、効く話だと思います。

よくある質問

Q. array_filterのあとで添字を0から振り直すにはどうしますか?
A. array_values()で包みます。array_values(array_filter($arr))と書くと、残った要素が0から連番のリストになります。

Q. 空文字とnullだけ消して0は残したいときは?
A. コールバックで条件を明示します。fn($v) => $v !== null && $v !== ” のように書くと、0や”0″は残ります。

Q. コールバックにキーも渡したいときは?
A. 第3引数にARRAY_FILTER_USE_BOTHを渡すと、コールバックの引数が「値、キー」の順で渡されます。キーだけならARRAY_FILTER_USE_KEYです。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です