Я бы не стал изобретать очередной велосипед, но те решения, которые мне попадались, имеют общую проблему — они корректно обрабатывают только символы базовой плоскости юникода.
Способ, в разных модификациях широко распространенный на просторах интернета, заключается в том, что результат работы ф-ции json_encode обрабатывается фильтром, заменяющим все вхождения \uXXXX на utf-8 символы. Например, так:
class Json{ static function json_encode($data){ return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function($val){ return mb_decode_numericentity(''.intval($val[1], 16).';', array(0, 0xffff, 0, 0xffff), 'utf-8'); }, json_encode($data) ); } }
И этот код работал… До тех пор, пока не понадобилось добавить поддержку юникодных emoji (эмотиконы были добавлены в стандарте Unicode 6), большинство из которых имеет коды более 0x1F000 (первая плоскость unicode).
Дело в том, что \u-последовательности имеют кодировку utf-16: слово (2 байта) на символ с кодом от 0x0000 до 0xFFFF (исключая «окно» 0xD800-0xDFFF) и 2 слова (4 байта) с кодами 0xD800-0xDFFF для символов с кодами более 0xFFFF.
Например, исходный юникод-символ с кодом 0x1f601, имеющий utf-8 представление "\xf0\x9f\x98\x81", будет преобразован функцией json_dencode в строку "\ud83d\ude01" и результатом вышеприведенной ф-ции будет строка "\xed\xa0\xbd\xed\xb8\x81". Вместо одного 4-х байтового символа получили два 3-х байтовых.
Таким образом, для нормальной обработки символов необходим анализ кодов и отдельное преобразование 2-х символьных \u-последовательностей. Например, так:
class Json{ static public $_code; static public function json_encode($data){ Json::$_code=0; return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function($val){ $val=hexdec($val[1]); if(Json::$_code){ $val=((Json::$_code&0x3FF)<<10)+($val&0x3FF)+0x10000; Json::$_code=0; }elseif($val>=0xD800&&$val<0xE000){ Json::$_code=$val; return ''; } return html_entity_decode(sprintf('%x;', $val), ENT_NOQUOTES, 'utf-8'); }, json_encode($data) ); } }
Данный вариант корректно преобразовывает любые utf-8 символы.
P.S. Я прекрасно понимаю, что вышеприведенный код далек от оптимального. Но он работает и с достаточной — для моих задач — производительностью. А сравнивать скорость работы всех придуманных вариантов просто лень. Вот, например, вариант, перекладывающий анализ на регулярное выражение:
class Json{ static public function json_encode($data){ return preg_replace_callback('/\\\\ud([89ab][0-9a-f]{2})\\\\ud([c-f][0-9a-f]{2})|\\\\u([0-9a-f]{4})/i', function($val){ return html_entity_decode(empty($val[3])? sprintf('%x;', ((hexdec($val[1])&0x3FF)<<10)+(hexdec($val[2])&0x3FF)+0x10000): ''.$val[3].';', ENT_NOQUOTES, 'utf-8'); }, json_encode($data)); } }
P.P.S. Вызовы html_entity_decode вставлены в callback-функцию потому, что обрабатываемые данные могут содержать html-код, включающий служебные html-сущности (‘<‘, ‘>’, ‘&’ и т.д.), которые не должны быть преобразованы в символы.
ссылка на оригинал статьи http://habrahabr.ru/post/195806/