【问题标题】:json_encode serialize null bytesjson_encode 序列化空字节
【发布时间】:2011-07-18 09:45:12
【问题描述】:

我今天遇到了这个serialize gotcha。来自 PHP.net 文档:

注意: 对象的私有成员具有附加到成员的类名 姓名;受保护成员的成员名称前有一个“*”。这些 前置值有两边都有空字节

我正在使用 debug_backtrace 为调试报告生成跟踪,得到json_encoded。在内部,它使用序列化程序为跟踪生成数据。

这是json_encode 的(部分)输出:

{"\u0000MyObject\u0000my_var":[]}

问题是json_decode 不能处理这个,它会抱怨空字节。

所以json_encode 愉快地写入了json_decode 无法解码的空字节。这对我来说似乎有点不可思议。我希望json_encode 负责必要的转义,或者至少json_decode 可以解析json_encode 生成的任何内容,但情况似乎并非如此。

我想我有几个解决方案:

  • 从跟踪中去除空字节,我对反序列化对象不太感兴趣,我只想要一个字符串表示。
  • 从跟踪中剥离私有变量。
  • 修复json_encode,使其不会产生空字节
  • 修复 json_decode 使其接受空字节

有人遇到过这个问题吗?您是如何解决的?


示例:

<?php
class MyClass {
    public $mypublic = 1;
    private $myprivate = 2;
    public function myfunc() {
        return debug_backtrace();
    }
}
$c = new MyClass();
$json = json_encode(call_user_func_array(array($c, "myfunc"), new MyClass()));
echo $json;
echo json_decode($json); // <-- Fatal error: Cannot access property started with '\0' in test.php on line 12

解决方案

由于 PHP 5.3 call_user_func_array 将在第二次发出警告 call_user_func_array 的参数不是数组。在此之前,您必须自己检查。

【问题讨论】:

    标签: php json


    【解决方案1】:

    (抱歉,作为评论可能会更好,因为它不能完全回答您的问题 - 但它有点太长了)


    我尝试使用 PHP 5.3 和 5.2 重现您所描述的内容,这就是我得到的:

    首先,让我们创建一个具有私有属性的类并实例化它:

    class A {
        public $pub = 10;
        private $priv = 20;
    }
    
    $a = new A();
    var_dump($a);
    

    这让我很兴奋:

    object(A)[1]
      public 'pub' => int 10
      private 'priv' => int 20
    


    现在,如果我serialize()我的对象:

    $serialized = serialize($a);
    var_dump($serialized);
    

    我明白了:

    string 'O:1:"A":2:{s:3:"pub";i:10;s:7:"�A�priv";i:20;}' (length=46)
    

    这与您所描述的差不多:null-bytes 围绕着 private-property 的名称。


    让我们继续json_encode()

    $jsoned = json_encode($serialized);
    var_dump($jsoned);
    

    正如你所说,这给了我一个带有一些 \u0000 的字符串:

    string '"O:1:\"A\":2:{s:3:\"pub\";i:10;s:7:\"\u0000A\u0000priv\";i:20;}"' (length=64)
    


    现在,如果我尝试json_decode() 这个字符串:

    $unjsoned = json_decode($jsoned);
    var_dump($unjsoned);
    

    这是我得到的:

    string 'O:1:"A":2:{s:3:"pub";i:10;s:7:"�A�priv";i:20;}' (length=46)
    

    => 空字节似乎没有丢失:它们是从 JSON 字符串正确重新创建的。


    并且,为此致电unserialize()

    $unserialized = unserialize($unjsoned);
    var_dump($unserialized);
    

    我找回了我最初拥有的对象:

    object(A)[2]
      public 'pub' => int 10
      private 'priv' => int 20
    

    所以,在序列化+编码和反编码+反序列化时,我似乎没有重现您的问题......

    我应该补充一点,我在这两个方面都找不到任何关于此类错误的信息:

    • php 的错误跟踪器,
    • 以及 json 扩展的 SVN 历史记录。



    现在,如果我尝试一个更复杂的对象,一个包含私有成员的类,它本身就是一个包含私有属性的对象:

    class A {
        private $priv;
        public function __construct() {
            $this->priv = new B();
        }
    }
    
    class B {
        private $b = 10;
    }
    


    我得到了完全相同的行为:一切正常——这是我得到的输出,当使用与以前完全相同的操作和 var_dump() 调用时:

    object(A)[1]
      private 'priv' => 
        object(B)[2]
          private 'b' => int 10
    
    string 'O:1:"A":1:{s:7:"�A�priv";O:1:"B":1:{s:4:"�B�b";i:10;}}' (length=54)
    
    string '"O:1:\"A\":1:{s:7:\"\u0000A\u0000priv\";O:1:\"B\":1:{s:4:\"\u0000B\u0000b\";i:10;}}"' (length=84)
    
    string 'O:1:"A":1:{s:7:"�A�priv";O:1:"B":1:{s:4:"�B�b";i:10;}}' (length=54)
    
    object(A)[3]
      private 'priv' => 
        object(B)[4]
          private 'b' => int 10
    

    在这里,我也无法重现您描述的问题。



    不过,如果我试试这个:

    var_dump( 
        unserialize( 
            json_decode('{"\u0000MyObject\u0000my_var":[]}')
        )
    );
    

    我确实遇到了麻烦:

    Fatal error: Cannot access property started with '\0'
    

    但是,想想看,如果我尝试“自己”解码它,我真的不明白你是如何得到这样一个 JSON 字符串的......

    您确定其他地方没有问题吗?像在编码过程中一样?

    【讨论】:

    • 我没有做序列化步骤。我只是在参考,似乎 json_encode 在编码私有变量时正在做类似于serialize 的事情。我在 json_encode 之前没有序列化的原因是因为我真的不知道我在编码什么(或者我真的不在乎)。我可以添加一个序列化步骤,但首先要使用 JSON,因为 JSON 本身就是一个序列化。至于如何获取 JSON 字符串?好吧,您只需对您创建的对象进行 json_encode(无需先序列化)
    • @Frits 哦,我虽然你在某处调用了一些serialize(),根据你所说的 - 以及空字节;;;实际上,json_encode() 没有保留 protectedprivate 属性:返回的字符串中只存在公共属性。 (我刚刚测试过;json_encode() 手册页上有很多关于此的 cmets)
    • 我已经弄清楚是什么导致了'wonkiness'。我追踪了代码,有一个call_user_func_array_array 在这里很重要)。 get 传入的参数不是数组。在我的实际代码中,它是一个实现CountableArrayAccessIteratorAggregate 的对象,但它仍然是一个对象。该对象有一个私有参数。我想现在的问题是......现在我该怎么办? :D
    • 我已经用我的解决方案修改了这个问题。
    • Whooo ^^ 我现在不能尝试,但是,我会尽可能(也许明天)尝试看看你的代码到底在做什么 ;-)
    【解决方案2】:

    您可以简单地使用正则表达式从 JSON 字符串中去除 \u0000,然后如果您对其进行解码,它应该没问题。

    【讨论】:

    • str_replace 有效。它不会改变数据被修改的事实。如果您愿意,您不能再次反序列化该对象。
    • JSON 可能包含'\\u0000' 包含空字节字符
    【解决方案3】:

    尝试将编码更改为 utf8。我假设您正在从数据库中获取数据

    mysql_query('SET CHARACTER SET utf8');
    

    它应该工作。如果不是,请在解码对象之前尝试转义空字符

    preg_replace('|\\u0000|', ' ', $json);
    

    如果不行,试试这个

    <?php
    $json = '{"\u0000MyObject\u0000my_var":[]}';
    $json = preg_replace('/\\\\u([0-9A-F]{4})/i', '', $json);
    $json = json_decode($json);
    print_r($json);
    

    http://sandbox.phpcode.eu/g/684be.php

    【讨论】:

    • 我不是。编码不是问题。空字节是由序列化程序有意生成的。理想情况下,我希望它们得以保存。
    • 不,这里不需要正则表达式。唯一有问题的编码是\u0000,其他都很好。谢谢你的尝试。当然,这个解决方案是有效的。我只是想知道哪个是最好的解决方案。
    • 我知道它有效,我将其列为已知解决方案。但它没有回答我的问题;)
    • “有人遇到过这个问题吗?你是怎么解决的?”
    猜你喜欢
    • 2015-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-29
    • 2012-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多