記事一覧はこちら

配列をjson_decodeしてもオブジェクトになるとは限らない

※ここで言うオブジェクトは$a->bでアクセス出来る値の事。

では問題です。以下のコードはどこでエラーが出るでしょうか。

var_dump( $a=json_decode( json_encode(array( ))));
var_dump( $b=json_decode( json_encode(array("0"=>"val","1"=>"val-1"))));
var_dump( $c=json_decode( json_encode(array("0"=>"val","2"=>"val-2"))));


$a->add="ok";
$b->add="ok";
$c->add="ok";


var_dump( $a);
var_dump( $b);
var_dump( $c);

答えは
$a->add="ok";
$b->add="ok";
の二箇所でした。もちろんokは代入されていません。

順番に見ていきます。
$a->add は$aオブジェクトにaddプロパティを追加します。ここでエラーが出るという事は、$aと$bがオブジェクトではないからです。 
しかし$aと$bはjson_decodeの結果ですし、戻り値が配列になる 第二引数は指定していません。しかし実際にエラーが起きているので、このjson_decodeはオブジェクト以外を返しているのです。

ではjson_decodeには何の値が渡されているか?
渡されている値はjson_encodeの結果です。その結果を直接見てみましょう。

print( json_encode(array( )));
print( json_encode(array("0"=>"val","1"=>"val-1")));
print( json_encode(array("0"=>"val","2"=>"val-2")));

[]
["val","val-1"]
{"0":"val","2":"val-2"}

json_encodeに渡す時は全て配列だったのですが、中身が無い キーが連続した数値 それ以外 の差があった為に、json_encodeで配列連想配列に区別されてしまったのです。
(配列と連想配列という名前が正しいか不明ですが、json_encodeの結果の記号が違う事が重要です) 
そもそも二番目の array("0"=>"val","1"=>"val-1")はphp上array("val","val-1")と等価です。===してもtrueです。

{"0":"val","2":"val-2"}の値がjson_decodeに渡された時はオブジェクトを返してくれるので、$c->addは問題なく実行されました。
しかし[]や["val","val-1"]がjson_decodeに渡された時は第二引数が指定されていようがいまいが、配列を返してしまうのです。

var_dump(  json_decode('[]')===json_decode('[]',true));//true

固定されていない配列をjson_encodeし、その後json_decodeしてオブジェクトを取得したつもりになっているコードはここでハマります。
PHP: json_decode - Manual json_decodeのマニュアルでも、この様に配列を例に取って第二引数が無いと配列になる。と示しているので勘違いしていました。

これを防ぐには、デフォルトで何か文字列のキーを入れておくこと。

配列で$a["b"] って書くより$a->bの方が書きやすいからオブジェクトを使おう!
二つの配列を合成するarray("a"=>"ok")+array("a"=>"no") がやりたいから配列を使おう!
と、データを保存する部分でオブジェクトと配列を何度も変換していたのでハマりました。 
何を今更と鼻で笑う人はともかく、一人でも同じ勘違いをしている人が救われるといいなとおもいます。