json_encode()で出力したjsonってユニコードエスケープされているのですが、出力したjsonを手でメンテしたりすることを考えると、utf-8になっている方がうれしいわけです。
PHP6だとunicode_encode()なるステキ関数が用意されているみたいですが、PHP5だと自前で変換するしかない。
http://d.hatena.ne.jp/iizukaw/20090422 というすばらしい記事があったのでここのunicode_encode()を使ってjsonのutf-8化をしていたのですが。
##問題
サロゲートペアという、1つの文字をunicode文字2文字分(4バイト)で表現する文字があって、上のunicode_encode()だとこれらの文字をうまいこと変換できませんでした。
正規表現"/\\\\u([0-9a-zA-Z]{4})/"
でユニコード文字をひとつずつ取り出してpack()してutf-8への変換をしているので、ユニコード2文字で1文字を表現するサロゲートペアがうまいこと変換できないんでしょうね。
##解決
もうちょっとキレイなやり方がありそうな気がするのですが、こうやって直しました。
まずは正規表現を"/((?:\\\\u[0-9a-zA-Z]{4})+)/"
にしてユニコード文字をひとまとめにしてゲットできるようにします。
で、受け取った文字を変換するところで
function encode_callback($matches) {
// \uいらない
$tmp = preg_replace("/\\\\u/", "", $matches[1]);
$fields = str_split($tmp, 4);
$s = "";
// 連結
foreach ($fields as $field) {
$s .= pack("H*", $field);
}
// ひとつながりのユニコード文字列をutf-8に変換
// !これだとサロゲートペアもうまいこと解釈してくれるみたい!
$char = mb_convert_encoding($s, "UTF-8", "UTF-16");
return $char;
}
とすることで無事変換できるようになりましたとさ。
2016.03.01追記
ものすごく長い文章をこの方法で変換すると、なんと php 自体がクラッシュしました。
// ものすごく長い日本語テキスト
$str = "";
for ($i = 0; $i < 100000; $i++) {
$str .= "あいうえお";
}
$jsonStr = json_encode($str);
$jsonStr2 = unicode_encode($jsonStr); // ★ ここで死亡
おとなしくPHPのバージョンをあげるのが良さそうです。