JSON Eval Trick in PHP_PHP教程
JSON Encode
JSON的编码并没有什么难度,要点就两个:
- 对Object、Array中的元素进行递归遍历,注意要将关联数组转换成JSON中的Object Literal。
- 对字符串内容中引号、换行符等特殊字符进行转义,并将非ASCII字符转换成Unicode转义序列的形式。
下面是JSON编码方法的实现:
/** * JSON Encode * @warn Any input string must be UTF-8 encoding * @param {Any} $data Any type object to serialize. * @return {String} serialized json string. */ function json_stringify($data) { if (is_null($data)) return 'null'; if (is_scalar($data)) return json_stringify_scalar($data); if (empty($data)) return '[]'; if (is_object($data)) { $data=get_object_vars($data); if (empty($data)) return '{}'; } $keys=array_keys($data); if ($keys===array_keys($keys)) { $data=array_map(__FUNCTION__,$data); return '['.join(',',$data).']'; } else {//不是有序数字下标的数组即视为关联数组 $a=array(); foreach ($data as $k=>$v) { $a[]=json_stringify_scalar(strval($k)).':'.json_stringify($v); } return '{'.join(',',$a).'}'; } } function json_stringify_scalar($v) { if (is_bool($v)) { $v = $v?'true':'false'; } else if (is_string($v)) { $v=addcslashes($v,"\t\n\r\"\/\\");//转义特殊字符 //将所有非ASCII字符转换成Unicode Escape格式 $v='"'.preg_replace_callback('|[^\x00-\x7F]+|','_unicode_escape',$v).'"'; } return (string)$v; } function _unicode_escape($s) { //Warning:字符串必须为UTF-8编码 $s=str_split(iconv('UTF-8','UCS-2BE',$s[0]),2); foreach ($s as $i=>$c) { $s[$i]=sprintf('\u%02x%02x',ord($c{0}),ord($c{1})); } return join('',$s); }
JSON Decode
而将JSON转换成PHP中的对象就没有那么简单了,自己写Parser进行词法分析、语法分析是很累人的。反观在JavaScript中实现JSON Parse就简单多了,因为JSON本身即是JavaScript语言的子集,直接使用eval方法,就能将JSON字符串转换成JS中的对象。
其实,看到JavaScript中的eval方法,实在不能不联想到PHP也有一个eval,它们的功能是类似的,都是将字符串当作代码运行。不同的是,JS中的eval执行JS代码,PHP的eval执行PHP代码。Trick就在这里:要让PHP能直接用eval方法解析JSON,只要将JSON代码转换成PHP格式的代码就行了!如对于下面的JSON:
{ "name":"CJ", "age":18, "tags":["PHP","JavaScript","Python","Haskell"], "life":{ "summary":"Too complex!" } }
只要将其转换成这样的PHP代码就能直接eval了:
(object)array( "name"=>"CJ", "age"=>18, "tags"=>array("PHP","JavaScript","Python","Haskell"), "life"=>(object)array( "summary"=>"Too complex!" ) )
而将JSON转换成PHP代码则只需很少的步骤与注意点:
最终的实现代码出人意料的简单:
/** * JSON Decode * @param {String} $s The string json data. * @param {Boolean} [$assoc=false] Return assoc array if $assoc is true. * @return decode result corresponding object. */ function json_parse($s,$assoc=false) { static $strings,$count=0; if (is_string($s)) { $s=trim($s); $strings=array(); //匹配字符串结束引号应该确保前面只能有偶数个'\' //如 "ab\"c"中 \" 不能被视为字符串结束引号 $s=preg_replace_callback('/"([\s\S]*?(?,__FUNCTION__,$s); //去除特殊字符后做简单的安全检测 $clean=str_replace(array('true','false','null','{','}','[',']',',',':','#','.'),'',$s); if ($clean && !is_numeric($clean)) {//可能是格式不正确的JSON、恶意代码 return NULL; } $s=str_replace( array('{','[',']','}',':','null'), //通过'(object)'类型转换将关联数组转换成stdClass instance array(($assoc?'':'(object)').'array(','array(',')',')','=>','NULL') ,$s); $s=preg_replace_callback('/#\d+#/',__FUNCTION__,$s); //抑制错误,如{3##}能通过上面的安全检测但却无法转换成正确的PHP代码 @$data=eval("return $s;"); $strings=$count=0;//GC return $data; } elseif (count($s)>1) {//存储字符串 $strings[]=_unicode_unescape(str_replace(array('$','\\/'),array('\\$','/'),$s[0])); return '#'.($count++).'#'; } else {//读取存储的值 $index=substr($s[0],1,strlen($s[0])-2); return $strings[$index]; } } function _unicode_unescape($data) { if (is_string($data)) { //匹配 Unicode escape时,需要注意匹配'\u'前面只能有偶数个'\',如'\\u5409'不应被匹配 return preg_replace_callback( '/(?, __FUNCTION__,$data); } return $data[1].iconv("UCS-2BE","UTF-8",chr(hexdec($data[2])).chr(hexdec($data[3]))); }
性能
和PHP 5.2之后的C实现的JSON方法相比,这里的实现自然要慢近百倍,根本不是一个数量级上的。 PEAR上有一个纯PHP实现的JSON库:Services_JSON。作为比较,我做了一个简单的性能对比测试,结果是,Services_JSON的encode方法比json_stringify
方法慢三四倍,而Services_JSON的decode方法更是比json_parse
方法慢十几倍。 对于json_stringify
方法,应该是主要得益于使用iconv先将UTF-8字符串转换成UCS-2BE,再转换成Unicode转义序列的形式,自然要比Services_JSON自己实现UTF-8到Unicode转义序列的转换性能更高。而json_parse
方法,虽然一般的递归下降Parser只需要扫描一次JSON字符串,而这里的实现会扫描多次字符串,但由于都是使用的PHP Native字符串方法,再加上eval这个非常规手段,最终性能上反而要高出好几倍。
原文地址:http://jex.im/programming/json-eval-trick-in-php.html