PHP
JSON

PHPの配列を安全にJSONシリアライズする

More than 1 year has passed since last update.

以前 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_encodeJSON_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が役に立つことって少ないんじゃないですかね。