json_encode序列化空字节

6
我今天遇到了这个坑点。从PHP.net文档中得知:serialize 函数有以下注意事项:
  • 对象的私有成员在成员名前面加上类名;受保护的成员在成员名前面加上 '*',而这些前缀值两侧都有null字节
我使用 debug_backtrace 为调试报告生成跟踪信息,然后对其进行 json_encode 编码。它内部使用序列化程序来生成跟踪数据。
以下是 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 的第二个参数不是数组时,会抛出警告。在此之前,您需要自行检查。

3个回答

7
抱歉,这可能更适合作为评论,因为它并不完全回答你的问题,但是对于这个问题来说有点太长了。
我尝试使用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)

这基本上就是您所描述的:在private属性的名称周围有那些null字节。


接下来让我们继续看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(不要先进行序列化)即可。 - Halcyon
@Frits Oh,我以为你在某个地方调用了serialize(),从你的话和空字节来看;实际上,json_encode()不会保留protectedprivate属性:只有公共属性存在于返回的字符串中。*(我刚刚测试过;在json_encode()手册页面上也有很多评论)* - Pascal MARTIN
我已经找出了导致“不稳定”的原因。我追踪了代码,发现有一个call_user_func_array(这里的_array非常重要)。传递的参数不是数组。在我的实际代码中,它是一个实现了CountableArrayAccessIteratorAggregate的对象,但仍然是一个对象。而且这个对象有一个私有参数。现在的问题是...我该怎么办? :D - Halcyon
@PascalMARTIN 你好!我也遇到了这个问题。据说,PHP json_decode 函数将 lambda 函数名称解析为 null+'lambda_'+counter... - Christian
值得注意的是,在json-c 1.3.2中,正确的空字节解码无法正常工作。该问题已在此处此处有所记录。 - Gray Fox
显示剩余2条评论

-2

尝试将编码更改为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


我不是。编码不是问题。空字节是序列化器有意产生的。理想情况下,我希望它们被保留。 - Halcyon
不,你这里不需要正则表达式。唯一有问题的编码是\u0000,其他的都没问题。感谢你的尝试。当然,这个解决方案是可行的。我只是想知道哪种方法更好。 - Halcyon
我知道它可以工作,我将其列为已知解决方案。但它并没有回答我的问题 ;) - Halcyon
"有人遇到过这个问题吗?你是如何解决的?" - genesis

-2
你可以简单地使用正则表达式从JSON字符串中去除\u0000,然后如果你解码它,它应该是可以的。

str_replace 能够工作。但这并不改变数据被修改的事实。如果需要,您将无法再次反序列化该对象。 - Halcyon
JSON 可能包含 '\\u0000',但不包含空字节字符。 - a55

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接