PHP5.2以前の環境でのjson_encode

WebアプリとAjaxJSON

一昔前のWebアプリの動作環境といえば、PC上のIEFirefoxだけをサポートすれば問題無かった。 その為、画面出力としてサーバー側で生成するHTMLもそれらに合わせれば良いだけなので、それほど問題にはならなかった。

が、現在の高機能スマートフォンに加えダブレット型PCなども普及し始めている状況では、 それらを含め複数の環境に対応する為に、サーバー側で一々それぞれに合わせたHTMLを吐くのではコストが掛かる。


その解法の一つとして、MVCのVC部分をクライアント側に移譲する事を可能にするJavaScriptが活用されている。

クライアントはそれぞれのページ表示に必要なデータをサーバーにリクエストし、 サーバーではそれがどのような形式で表示されるのかは関知せず、要求されたデータだけを返す。 そこで利用されるのが、所謂Ajaxと呼ばれる機能であり、サーバーからクライアントに渡されるデータの形式として広く利用されているのがJSONとなる。

この一連のJavaScriptによるページ更新では基本的にページ遷移を伴わない為に、サーバー/クライアント側の負荷軽減と、 ネットワークトラフィックの削減、レスポンス速度向上によるユーザビリティ向上が望める。
# 中途半端なJavaScript利用の失敗例として、逆に負荷が増し機能性も落ちるなどのようなケースもあり、
# 実際にそのような残念サイトも存在するが。


一般ユーザー向けサイトでのJavaScript

ここで問題となるのが一般ユーザーのJavaScript利用率であるが、JavaScriptが利用出来ない/意図的にOffにしているユーザーは既に2%ほどだとい調査もある。 How many users have JavaScript disabled?

JavaScript(に加え色々と)利用不可な所謂ケータイが現在も大きなシェアを持っているであろう日本の状況は多少異なると思われるが、 それでもJavaScriptが動作する環境は広く普及し、尚且つJavaScriptが有効である事を前提とした大手サイトが多い事もJavaScriptの援護となっている。
# と言いつつ個人的にはFirefoxのNoScriptを常用しているので、
# 殆どのサイトのdefaultではJavaScriptOFFの状態ではあるけれども。;)


特定ユーザー向けサイトでのJavaScript

このような状況ではあるが、実際には一般ユーザー向けサイトでは、フルJavaScript動作のWebアプリとするのは残念ながら時期尚早。 が、利用環境をある程度限定出来るBtoBサイトやWebアプリの管理機能や社内向けなどでは既に広くJavaScript化が進んでいる。

という訳で、現在行っているECサイト案件でも管理画面では全面的にJavaScriptを利用しており、 サーバーからクライアントへのデータ形式にはPHPスクリプトで生成したJSONを使っている。


バージョン間の差異

現在の開発環境はPHP5.3.xを使っているが、まだPHP5.2系の環境も多い為に基本的には5.2.xでの動作を前提としている。 が、そのECサイトPHPのバージョンは5.1.x。


現在のバージョンが5.1.xなのは最初から判っていた事ではあるが、 4.xから5.xのようなメジャーバージョンが上がるようなアップデートを躊躇するのは判るけれども、 今時5.1.xのような中途半端に古いバージョンを使い続ける理由も無いだろう。 というような甘い見通しを立てて、実装時についでにPHPのバージョンアップも行おうと目論んでいたが、 色々と理由を提示されてどうもそれが難しい模様。

過去に遭遇したらしい環境移行でのトラブルがトラウマ?となっている様子も伺え、 現在正常に動いている物をわざわざ変更する必要は無いと考えるのも無理は無いと受け取り、 とりあえず5.1.x上での動作確認を行う事にする。
# 自分に都合の良い環境を前提とするのでは無く、開発環境を実動環境に合わせるのが常識なのだけれど、
# 動作環境の選択で比較的融通が利くWeb系故の甘さという感は否めなく、今回の反省点でもある。


json_encode

ここで問題となったのが、クライアントに渡すJSONデータを生成するjson_encodeが5.1.xで利用不可な点。

前述のように、管理機能でのサーバーからクライアントへのAjaxのレスポンスとしてのデータ送信ではJSONを利用しているので、 json_encodeは非常に重要で必須なコア機能となっている。


が、無い物は仕方無く、無ければ自分で書けば良いのはプログラマ的な常識なので、その方向で対応を行う事にする。

という訳で、PECLを使うのもアレだと思いつつ、json_encodeの動作や上記ページのコメントやServices_JSONのソースを読み、 簡易版のjson_encodeを書いて見た。

MITライセンスの元での公開なので利用はご自由に。


if ( !function_exists('json_encode') )
{
  function json_encode( $array ) 
  {
    if( !is_array($array) )
      return _js_encValue( $array );

    $assoc = FALSE;
    if ( array_diff(array_keys($array),range(0,count($array)-1)) )
      $assoc = TRUE;

    $data = array();
    foreach( $array as $key=>$value )
    {
      if ( $assoc )
      {
        if ( !is_numeric($key) )
          $key = preg_replace('/(["\\\])/u','\\\\$1',$key );
        $key = '"'.$key.'"';
      }
      $value = _js_encValue( $value );
      $data[] = ($assoc ? "$key:$value" : $value);
    }
    if ( $assoc )
      return "{".implode(',',$data)."}";
    else
      return "[".implode(',',$data)."]";
  }

  function _js_encValue( $value )
  {
    if ( is_array($value) )
      return json_encode( $value );
    else if ( is_bool($value) )
      return ($value ? 'true' : 'false');
    else if ( $value === NULL )
      return 'null';
    else if ( is_string($value) )
      return '"'._js_toU16Entities($value).'"';
    else if ( is_numeric($value) )
      return $value;
    return '"'.$value.'"';
  }

  function _js_toU16Entities( $string )
  {
    $len = mb_strlen( $string, 'UTF-8' );
    $str = '';
    $strAry = preg_split( '//u', $string );
    for ( $idx=0, $len=count($strAry); $idx < $len; $idx++ )
    {
      $code = $strAry[$idx];
      if ( $code === '' ) continue;
      if ( strlen($code) > 1 )
      {
        $hex = bin2hex( mb_convert_encoding($code,'UTF-16','UTF-8') );
        if ( strlen($hex) == 8 ) // surrogate pair
          $str .= vsprintf( '\u%04s\u%04s', str_split($hex,4) );
        else
          $str .= sprintf( '\u%04s', $hex );
      } else {
        switch ( $code )
        {
          case '"':
          case '/':
          case '\\':
            $code = '\\'.$code;
        }
        $str .= $code;
      }
    }
    $str = str_replace( array("\r\n","\r","\n"), array('\r\n','\r','\n'), $str );
    return $str;
  }
}
変更点: 2011/02/16

改行コードがエンコードされておらず、IEでのデコードに失敗する場合があった為、 改行コードもエンコードするように修正。
また、画像データなどサイズの大きいデータをエンコードする場合には非常に遅かったのを多少改善。