読者です 読者をやめる 読者になる 読者になる

HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

json_encode/json_decodeに潜む罠

PHP

あるところ(何)で、json_decodeした結果がオブジェクトになるときと配列になるときがあるという問題が発生したらしい(ちなみに、json_decodeの第二引数にtrueを指定しないからという話ではない)。
例えば次のような感じ。

<?php
$data = array(
    'name' => 'hogehoge',
    'list' => array('a', 'c', 'b'),
);
$data_json = json_encode($data);
// どこか(何)を通って引渡し
$data_client = json_decode($data_json);
var_dump($data_client->name);
var_dump($data_client->list); // ここが、配列のときとオブジェクトのときがある

実際には、json_encodeする前にいろんな処理をしているので、ついでにいろんなパターンについてjson_encodeをしてみる。

<?php
/**
 * 指定された文字列と配列を持つ連想配列を生成し、
 * JSON形式に変換する。
 * 
 * フィルター関数(function func(& $ary))を指定すると、
 * 指定した配列にその関数を適用する。
 */
function genJson($name, array $list, $filter = null) {
    $data = array(
        'name' => sprintf("%-12s", $name),
        'list' => $list,
    );
    if (isset($filter)) {
        $filter($data['list']);
    }
    return json_encode($data);
}
// シンプルな配列
print(genJson('simple array',
            array('a', 'c', 'b')) . "\n");
// シンプルな配列から途中の要素を削除(unset)
print(genJson('unset[1]',
            array('a', 'c', 'b'),
            function(&$ary) {
                unset($ary[1]);
            }) . "\n");
// シンプルな配列から途中の要素を削除(array_splice)
print(genJson('splice[1]',
            array('a', 'c', 'b'),
            function(&$ary) {
                array_splice($ary, 1, 1);
            }) . "\n");
// シンプルな配列から末尾の要素を削除(unset)
print(genJson('unset[2]',
            array('a', 'c', 'b'),
            function(&$ary) {
                unset($ary[2]);
            }) . "\n");
// シンプルな配列にasortを適用
print(genJson('asort',
            array('a', 'c', 'b'),
            function(&$ary) {
                asort($ary);
            }) . "\n");
// 整数をキーとする連想配列
print(genJson('asoc array',
            array(0 => 'a', 2 => 'c', 1 => 'b')) . "\n");
// 整数をキーとする連想配列にksortを適用
print(genJson('ksort',
            array(0 => 'a', 2 => 'c', 1 => 'b'),
            function (&$ary) {
                ksort($ary);
            }) . "\n");
// 整数をキーとする連想配列の値だけを取り出した新たな配列
print(genJson('values',
            array(0 => 'a', 2 => 'c', 1 => 'b'),
            function(&$ary) {
                $ary = array_values($ary);
            }) . "\n");

すると、実行結果は次のようになる。

{"name":"simple array","list":["a","c","b"]}
{"name":"unset[1]    ","list":{"0":"a","2":"b"}}
{"name":"splice[1]   ","list":["a","b"]}
{"name":"unset[2]    ","list":["a","c"]}
{"name":"asort       ","list":{"0":"a","2":"b","1":"c"}}
{"name":"asoc array  ","list":{"0":"a","2":"c","1":"b"}}
{"name":"ksort       ","list":["a","b","c"]}
{"name":"values      ","list":["a","c","b"]}

確かに、json_encodeした時点で、配列の場合とオブジェクトの場合が存在する。
ためしに、1番目と2番目の結果をjson_decodeしてみると‥

<?php
$jsons = array(
    '{"name":"simple array","list":["a","c","b"]}',
    '{"name":"unset[1]    ","list":{"0":"a","2":"b"}}',
);
print('--------------------------------' . "\n");
foreach ($jsons as $json) {
    print($json . "\n");
    $obj = json_decode($json);
    var_dump($obj->list);
    print('--------------------------------' . "\n");
}

実行結果は、次のようになる。

--------------------------------
{"name":"simple array","list":["a","c","b"]}
array(3) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "c"
  [2]=>
  string(1) "b"
}
--------------------------------
{"name":"unset[1]    ","list":{"0":"a","2":"b"}}
object(stdClass)#4 (2) {
  ["0"]=>
  string(1) "a"
  ["2"]=>
  string(1) "b"
}
--------------------------------


ということで、今回調べたjson_encode/json_decodeのように、配列のキーが0からの連番の添え字になっていることが重要になってくるケースでは、

  • 0からの連番になっていることが保証されている配列で、どの位置の要素を削除するかが明確な場合は、array_splice関数で要素を削除するようにする
    • ⇒大量に削除する場合は、unsetしておいて、あとでarray_valuesを使うのがいいかも(未確認)
  • ソートなどが絡んできて何がどうなるかわからない場合は、最後にarray_valuesでキーをクリアして連番の添え字にする
  • 添え字と値の関係を崩したらまずい場合は、ksortを使用してキーでソートしておく

という対処が必要、と。

  • 0からの連番にできない
  • 添え字と値の関係は崩したくないし、ソートもしたくない

という場合はあきらめる、と(謎)。