以前 PHPのlist()はタプル展開のための機能 - Qiita にも書きましたが、PHPの配列型は非常に多くの機能はあり、0から始まる連番のインデックスを持つ「配列」としての側面と、ほかの言語で連想リスト(Association list, alist)やハッシュマップ、辞書などと呼ばれるコレクションとしての側面を合せ持ちます。
この仕様はとてもいいところと悪いところがあるのですが、PHPの型をPHP以外に出力(シリアライズ)するときには若干困ることがあります。たとへば、PHPの配列をjson_encode
でJSONにシリアライズするときなどです。
問題
<?php
$num = range(1, 10);
echo json_encode($num).PHP_EOL;
// => [1,2,3,4,5,6,7,8,9,10]
// これは想定通り
$num = range(1, 10);
// 奇数ならtrueを返す函数
$is_odd = function ($n) { return $n % 2 === 1; };
$num = array_filter($num, $is_odd);
echo json_encode($num).PHP_EOL;
// => {"0":1,"2":3,"4":5,"6":7,"8":9}
// あれれー
解決策
array_values
です… 配列にarray_values
を掛けると、純粋な配列であることを保証できるようになります… かつ、最初から連番の配列にarray_values
を使っても安全です…
逆に、数値で連番であるがJSONにはオブジェクト型にしてやりたい、といふときはjson_encode
にJSON_FORCE_OBJECT
フラグを立ててやるといいです。
<?php
/**
* @param array $value
* @param int $options
* @param int $depth
* @return string JSON
* @link http://php.net/manual/function.json-encode.php
*/
function json_encode_array(array $value$options = 0, $depth = 512)
{
return json_encode(array_values($value, $options, $depth));
}
/**
* @param array|object $value
* @param int $options
* @param int $depth
* @return string JSON
* @link http://php.net/manual/function.json-encode.php
*/
function json_encode_object($value, $options = 0, $depth = 512)
{
$options |= JSON_FORCE_OBJECT;
return json_encode($value, $options, $depth);
}
これでハッピーになれますね!
おまけ
JSONを利用してるみなさまには釈迦に説法なのですが、JSONの仕様によれば「ほかの言語で連想リスト(Association list, alist)やハッシュマップ、辞書などと呼ばれるコレクション」に相当する、 {"key": "value"}
のようなものの名前(型)は「object」です。
ってことは、json_decode
に第二引数を渡さなかったときに、デフォルトでstdClass
になるのも自然… ってそんなわけあるかstdClass
はちょっとつらい。
幸ひにしてPHPの配列とstdClass
はキャストで簡単に相互変換できるからいいんですけどね、いいんですけどね。
あとがき
PHP - 文字列配列から空文字を除去するバッドノウハウ - Qiita にも似たようなこと書いてた。
補足
すっかり忘れてたんだけどJSON_FORCE_OBJECT
フラグを立てると、要素も再帰的にオブジェクトになるので、場合によってはコレジャナイ結果を引き起すことがあるかも。ってわけで今回紹介したjson_encode_object
が役に立つことって少ないんじゃないですかね。