Rumah >php教程 >php手册 >JSON Eval Trick in PHP

JSON Eval Trick in PHP

WBOY
WBOYasal
2016-06-13 10:17:43959semak imbas

 

PHP从5.2版本开始提供json_encode()和json_decode()函数,分别用于JSON的序例化和反序列化。不幸的是,现在仍然有许多主机运行着PHP5.2之前的版本,所以就不得不自己动手写JSON解析了。

JSON Encode

JSON的编码并没有什么难度,要点就两个:

  • 对Object、Array中的元素进行递归遍历,注意要将关联数组转换成JSON中的Object Literal。
  • 对字符串内容中引号、换行符等特殊字符进行转义,并将非ASCII字符转换成Unicode转义序列的形式。

下面是JSON编码方法的实现:

<span class="sd">/**</span>
<span class="sd"> * JSON Encode</span>
<span class="sd"> * @warn Any input string must be UTF-8 encoding</span>
<span class="sd"> * @param {Any} $data Any type object to serialize.</span>
<span class="sd"> * @return {String} serialized json string.</span>
<span class="sd"> */</span>
<span class="k">function</span> <span class="nf">json_stringify</span><span class="p">(</span><span class="nv">$data</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nb">is_null</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="k">return</span> <span class="s1">'null'</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="nb">is_scalar</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="k">return</span> <span class="nx">json_stringify_scalar</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="k">return</span> <span class="s1">'[]'</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="nb">is_object</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="p">{</span>
    <span class="nv">$data</span><span class="o">=</span><span class="nb">get_object_vars</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="k">return</span> <span class="s1">'{}'</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nv">$keys</span><span class="o">=</span><span class="nb">array_keys</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="nv">$keys</span><span class="o">===</span><span class="nb">array_keys</span><span class="p">(</span><span class="nv">$keys</span><span class="p">))</span> <span class="p">{</span>
    <span class="nv">$data</span><span class="o">=</span><span class="nb">array_map</span><span class="p">(</span><span class="nx">__FUNCTION__</span><span class="p">,</span><span class="nv">$data</span><span class="p">);</span>
    <span class="k">return</span> <span class="s1">'['</span><span class="o">.</span><span class="nb">join</span><span class="p">(</span><span class="s1">','</span><span class="p">,</span><span class="nv">$data</span><span class="p">)</span><span class="o">.</span><span class="s1">']'</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span><span class="c1">//不是有序数字下标的数组即视为关联数组</span>
    <span class="nv">$a</span><span class="o">=</span><span class="k">array</span><span class="p">();</span>
    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$data</span> <span class="k">as</span> <span class="nv">$k</span><span class="o">=></span><span class="nv">$v</span><span class="p">)</span> <span class="p">{</span>
      <span class="nv">$a</span><span class="p">[]</span><span class="o">=</span><span class="nx">json_stringify_scalar</span><span class="p">(</span><span class="nb">strval</span><span class="p">(</span><span class="nv">$k</span><span class="p">))</span><span class="o">.</span><span class="s1">':'</span><span class="o">.</span><span class="nx">json_stringify</span><span class="p">(</span><span class="nv">$v</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="s1">'{'</span><span class="o">.</span><span class="nb">join</span><span class="p">(</span><span class="s1">','</span><span class="p">,</span><span class="nv">$a</span><span class="p">)</span><span class="o">.</span><span class="s1">'}'</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">function</span> <span class="nf">json_stringify_scalar</span><span class="p">(</span><span class="nv">$v</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nb">is_bool</span><span class="p">(</span><span class="nv">$v</span><span class="p">))</span> <span class="p">{</span>
    <span class="nv">$v</span> <span class="o">=</span> <span class="nv">$v</span><span class="o">?</span><span class="s1">'true'</span><span class="o">:</span><span class="s1">'false'</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nb">is_string</span><span class="p">(</span><span class="nv">$v</span><span class="p">))</span> <span class="p">{</span>
    <span class="nv">$v</span><span class="o">=</span><span class="nb">addcslashes</span><span class="p">(</span><span class="nv">$v</span><span class="p">,</span><span class="s2">"</span><span class="se">\t\n\r\"</span><span class="s2">\/</span><span class="se">\\</span><span class="s2">"</span><span class="p">);</span><span class="c1">//转义特殊字符</span>
    <span class="c1">//将所有非ASCII字符转换成Unicode Escape格式</span>
    <span class="nv">$v</span><span class="o">=</span><span class="s1">'"'</span><span class="o">.</span><span class="nb">preg_replace_callback</span><span class="p">(</span><span class="s1">'|[^\x00-\x7F]+|'</span><span class="p">,</span><span class="s1">'_unicode_escape'</span><span class="p">,</span><span class="nv">$v</span><span class="p">)</span><span class="o">.</span><span class="s1">'"'</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="p">(</span><span class="nx">string</span><span class="p">)</span><span class="nv">$v</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">function</span> <span class="nf">_unicode_escape</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">//Warning:字符串必须为UTF-8编码</span>
  <span class="nv">$s</span><span class="o">=</span><span class="nb">str_split</span><span class="p">(</span><span class="nb">iconv</span><span class="p">(</span><span class="s1">'UTF-8'</span><span class="p">,</span><span class="s1">'UCS-2BE'</span><span class="p">,</span><span class="nv">$s</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span><span class="mi">2</span><span class="p">);</span>
  <span class="k">foreach</span> <span class="p">(</span><span class="nv">$s</span> <span class="k">as</span> <span class="nv">$i</span><span class="o">=></span><span class="nv">$c</span><span class="p">)</span> <span class="p">{</span>
    <span class="nv">$s</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span><span class="o">=</span><span class="nb">sprintf</span><span class="p">(</span><span class="s1">'\u%02x%02x'</span><span class="p">,</span><span class="nb">ord</span><span class="p">(</span><span class="nv">$c</span><span class="p">{</span><span class="mi">0</span><span class="p">}),</span><span class="nb">ord</span><span class="p">(</span><span class="nv">$c</span><span class="p">{</span><span class="mi">1</span><span class="p">}));</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nb">join</span><span class="p">(</span><span class="s1">''</span><span class="p">,</span><span class="nv">$s</span><span class="p">);</span>
<span class="p">}</span>

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:

<span class="p">{</span>
  <span class="nt">"name"</span><span class="p">:</span><span class="s2">"CJ"</span><span class="p">,</span>
  <span class="nt">"age"</span><span class="p">:</span><span class="mi">18</span><span class="p">,</span>
  <span class="nt">"tags"</span><span class="p">:[</span><span class="s2">"PHP"</span><span class="p">,</span><span class="s2">"JavaScript"</span><span class="p">,</span><span class="s2">"Python"</span><span class="p">,</span><span class="s2">"Haskell"</span><span class="p">],</span>
  <span class="nt">"life"</span><span class="p">:{</span>
    <span class="nt">"summary"</span><span class="p">:</span><span class="s2">"Too complex!"</span>
  <span class="p">}</span>
<span class="p">}</span>

只要将其转换成这样的PHP代码就能直接eval了:

<span class="p">(</span><span class="nx">object</span><span class="p">)</span><span class="k">array</span><span class="p">(</span>
  <span class="s2">"name"</span><span class="o">=></span><span class="s2">"CJ"</span><span class="p">,</span>
  <span class="s2">"age"</span><span class="o">=></span><span class="mi">18</span><span class="p">,</span>
  <span class="s2">"tags"</span><span class="o">=></span><span class="k">array</span><span class="p">(</span><span class="s2">"PHP"</span><span class="p">,</span><span class="s2">"JavaScript"</span><span class="p">,</span><span class="s2">"Python"</span><span class="p">,</span><span class="s2">"Haskell"</span><span class="p">),</span>
  <span class="s2">"life"</span><span class="o">=></span><span class="p">(</span><span class="nx">object</span><span class="p">)</span><span class="k">array</span><span class="p">(</span>
    <span class="s2">"summary"</span><span class="o">=></span><span class="s2">"Too complex!"</span>
  <span class="p">)</span>
<span class="p">)</span>

而将JSON转换成PHP代码则只需很少的步骤与注意点:

最终的实现代码出人意料的简单:

<span class="sd">/**</span>
<span class="sd"> * JSON Decode</span>
<span class="sd"> * @param {String} $s The string json data.</span>
<span class="sd"> * @param {Boolean} [$assoc=false] Return assoc array if $assoc is true.</span>
<span class="sd"> * @return decode result corresponding object.</span>
<span class="sd"> */</span>
<span class="k">function</span> <span class="nf">json_parse</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="nv">$assoc</span><span class="o">=</span><span class="k">false</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">static</span> <span class="nv">$strings</span><span class="p">,</span><span class="nv">$count</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="nb">is_string</span><span class="p">(</span><span class="nv">$s</span><span class="p">))</span> <span class="p">{</span>
    <span class="nv">$s</span><span class="o">=</span><span class="nx">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">);</span>
    <span class="nv">$strings</span><span class="o">=</span><span class="k">array</span><span class="p">();</span>
    <span class="c1">//匹配字符串结束引号应该确保前面只能有偶数个'\'</span>
    <span class="c1">//如 "ab\"c"中 \" 不能被视为字符串结束引号</span>
    <span class="nv">$s</span><span class="o">=</span><span class="nb">preg_replace_callback</span><span class="p">(</span><span class="s1">'/"([\s\S]*?(?<!\\\\)(?:\\\\\\\\)*)"/'</span><span class="p">,</span><span class="nx">__FUNCTION__</span><span class="p">,</span><span class="nv">$s</span><span class="p">);</span>
    <span class="c1">//去除特殊字符后做简单的安全检测</span>
    <span class="nv">$clean</span><span class="o">=</span><span class="nb">str_replace</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'true'</span><span class="p">,</span><span class="s1">'false'</span><span class="p">,</span><span class="s1">'null'</span><span class="p">,</span><span class="s1">'{'</span><span class="p">,</span><span class="s1">'}'</span><span class="p">,</span><span class="s1">'['</span><span class="p">,</span><span class="s1">']'</span><span class="p">,</span><span class="s1">','</span><span class="p">,</span><span class="s1">':'</span><span class="p">,</span><span class="s1">'#'</span><span class="p">,</span><span class="s1">'.'</span><span class="p">),</span><span class="s1">''</span><span class="p">,</span><span class="nv">$s</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nv">$clean</span> <span class="o">&&</span> <span class="o">!</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$clean</span><span class="p">))</span> <span class="p">{</span><span class="c1">//可能是格式不正确的JSON、恶意代码</span>
      <span class="k">return</span> <span class="k">NULL</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nv">$s</span><span class="o">=</span><span class="nb">str_replace</span><span class="p">(</span>
      <span class="k">array</span><span class="p">(</span><span class="s1">'{'</span><span class="p">,</span><span class="s1">'['</span><span class="p">,</span><span class="s1">']'</span><span class="p">,</span><span class="s1">'}'</span><span class="p">,</span><span class="s1">':'</span><span class="p">,</span><span class="s1">'null'</span><span class="p">),</span>
      <span class="c1">//通过'(object)'类型转换将关联数组转换成stdClass instance</span>
      <span class="k">array</span><span class="p">((</span><span class="nv">$assoc</span><span class="o">?</span><span class="s1">''</span><span class="o">:</span><span class="s1">'(object)'</span><span class="p">)</span><span class="o">.</span><span class="s1">'array('</span><span class="p">,</span><span class="s1">'array('</span><span class="p">,</span><span class="s1">')'</span><span class="p">,</span><span class="s1">')'</span><span class="p">,</span><span class="s1">'=>'</span><span class="p">,</span><span class="s1">'NULL'</span><span class="p">)</span>
      <span class="p">,</span><span class="nv">$s</span><span class="p">);</span>
    <span class="nv">$s</span><span class="o">=</span><span class="nb">preg_replace_callback</span><span class="p">(</span><span class="s1">'/#\d+#/'</span><span class="p">,</span><span class="nx">__FUNCTION__</span><span class="p">,</span><span class="nv">$s</span><span class="p">);</span>
    <span class="c1">//抑制错误,如{3##}能通过上面的安全检测但却无法转换成正确的PHP代码</span>
    <span class="o">@</span><span class="nv">$data</span><span class="o">=</span><span class="k">eval</span><span class="p">(</span><span class="s2">"return </span><span class="si">$s</span><span class="s2">;"</span><span class="p">);</span>
    <span class="nv">$strings</span><span class="o">=</span><span class="nv">$count</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="c1">//GC</span>
    <span class="k">return</span> <span class="nv">$data</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nb">count</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">></span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span><span class="c1">//存储字符串</span>
    <span class="nv">$strings</span><span class="p">[]</span><span class="o">=</span><span class="nx">_unicode_unescape</span><span class="p">(</span><span class="nb">str_replace</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'$'</span><span class="p">,</span><span class="s1">'\\/'</span><span class="p">),</span><span class="k">array</span><span class="p">(</span><span class="s1">'\\$'</span><span class="p">,</span><span class="s1">'/'</span><span class="p">),</span><span class="nv">$s</span><span class="p">[</span><span class="mi">0</span><span class="p">]));</span>
    <span class="k">return</span> <span class="s1">'#'</span><span class="o">.</span><span class="p">(</span><span class="nv">$count</span><span class="o">++</span><span class="p">)</span><span class="o">.</span><span class="s1">'#'</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span><span class="c1">//读取存储的值</span>
    <span class="nv">$index</span><span class="o">=</span><span class="nx">substr</span><span class="p">(</span><span class="nv">$s</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="mi">1</span><span class="p">,</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="o">-</span><span class="mi">2</span><span class="p">);</span>
    <span class="k">return</span> <span class="nv">$strings</span><span class="p">[</span><span class="nv">$index</span><span class="p">];</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">function</span> <span class="nf">_unicode_unescape</span><span class="p">(</span><span class="nv">$data</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nb">is_string</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="p">{</span>
    <span class="c1">//匹配 Unicode escape时,需要注意匹配'\u'前面只能有偶数个'\',如'\\u5409'不应被匹配</span>
    <span class="k">return</span> <span class="nb">preg_replace_callback</span><span class="p">(</span>
           <span class="s1">'/(?<!\\\\)((?:\\\\\\\\)*)\\\\u([a-f0-9]{2})([a-f0-9]{2})/i'</span><span class="p">,</span>
           <span class="nx">__FUNCTION__</span><span class="p">,</span><span class="nv">$data</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nv">$data</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="nb">iconv</span><span class="p">(</span><span class="s2">"UCS-2BE"</span><span class="p">,</span><span class="s2">"UTF-8"</span><span class="p">,</span><span class="nb">chr</span><span class="p">(</span><span class="nx">hexdec</span><span class="p">(</span><span class="nv">$data</span><span class="p">[</span><span class="mi">2</span><span class="p">]))</span><span class="o">.</span><span class="nb">chr</span><span class="p">(</span><span class="nx">hexdec</span><span class="p">(</span><span class="nv">$data</span><span class="p">[</span><span class="mi">3</span><span class="p">])));</span>
<span class="p">}</span>

性能

和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

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn