将 PHP 对象序列化为 JSON
因此,当我偶然发现新的 JsonSerialized 接口。不过它只有 PHP >= 5.4,而且我在 5.3.x 环境中运行。
PHP 是如何实现这种功能的? 5.4?
我还没有太多使用 JSON,但我正在尝试支持应用程序中的 API 层,并将数据对象(否则会发送到视图)转储到 JSON 中完美的。
如果我尝试直接序列化对象,它会返回一个空的 JSON 字符串;这是因为我假设 json_encode() 不知道该对象到底要做什么。我应该递归地将对象减少到数组中,然后对其进行编码吗?
示例
$data = new Mf_Data();
$data->foo->bar['hello'] = 'world';
echo json_encode($data)
生成一个空对象:
{}
var_dump($data)
但是,按预期工作:
object(Mf_Data)#1 (5) {
["_values":"Mf_Data":private]=>
array(0) {
}
["_children":"Mf_Data":private]=>
array(1) {
[0]=>
array(1) {
["foo"]=>
object(Mf_Data)#2 (5) {
["_values":"Mf_Data":private]=>
array(0) {
}
["_children":"Mf_Data":private]=>
array(1) {
[0]=>
array(1) {
["bar"]=>
object(Mf_Data)#3 (5) {
["_values":"Mf_Data":private]=>
array(1) {
[0]=>
array(1) {
["hello"]=>
string(5) "world"
}
}
["_children":"Mf_Data":private]=>
array(0) {
}
["_parent":"Mf_Data":private]=>
*RECURSION*
["_key":"Mf_Data":private]=>
string(3) "bar"
["_index":"Mf_Data":private]=>
int(0)
}
}
}
["_parent":"Mf_Data":private]=>
*RECURSION*
["_key":"Mf_Data":private]=>
string(3) "foo"
["_index":"Mf_Data":private]=>
int(0)
}
}
}
["_parent":"Mf_Data":private]=>
NULL
["_key":"Mf_Data":private]=>
NULL
["_index":"Mf_Data":private]=>
int(0)
}
附录
1)
这就是我为 Mf_Data
类设计的 toArray()
函数:
public function toArray()
{
$array = (array) $this;
array_walk_recursive($array, function (&$property) {
if ($property instanceof Mf_Data) {
$property = $property->toArray();
}
});
return $array;
}
但是,由于 Mf_Data
对象也有一个参考他们的父母(包含)对象,这会因递归而失败。当我删除 _parent
引用时,它就像一个魅力。
2)
只是为了跟进,我使用的转换复杂树节点对象的最终函数是:
// class name - Mf_Data
// exlcuded properties - $_parent, $_index
public function toArray()
{
$array = get_object_vars($this);
unset($array['_parent'], $array['_index']);
array_walk_recursive($array, function (&$property) {
if (is_object($property) && method_exists($property, 'toArray')) {
$property = $property->toArray();
}
});
return $array;
}
3)
我再次跟进,实现更清晰一些。使用接口进行 instanceof
检查似乎比 method_exists()
干净得多(但是 method_exists()
会横切继承/实现< /em>)。
使用 unset() 似乎也有点混乱,似乎应该将逻辑重构为另一个方法。但是,此实现确实复制属性数组(由于array_diff_key
),因此需要考虑一些事情。
interface ToMapInterface
{
function toMap();
function getToMapProperties();
}
class Node implements ToMapInterface
{
private $index;
private $parent;
private $values = array();
public function toMap()
{
$array = $this->getToMapProperties();
array_walk_recursive($array, function (&$value) {
if ($value instanceof ToMapInterface) {
$value = $value->toMap();
}
});
return $array;
}
public function getToMapProperties()
{
return array_diff_key(get_object_vars($this), array_flip(array(
'index', 'parent'
)));
}
}
So I was wandering around php.net for information about serializing PHP objects to JSON, when I stumbled across the new JsonSerializable Interface. It's only PHP >= 5.4 though, and I'm running in a 5.3.x environment.
How is this sort of functionality achieved PHP < 5.4?
I've not worked much with JSON yet, but I'm trying to support an API layer in an application, and dumping the data object (that would otherwise be sent to the view) into JSON would be perfect.
If I attempt to serialize the object directly, it returns an empty JSON string; which is because I assume json_encode()
doesn't know what the heck to do with the object. Should I recursively reduce the object into an array, and then encode that?
Example
$data = new Mf_Data();
$data->foo->bar['hello'] = 'world';
echo json_encode($data)
produces an empty object:
{}
var_dump($data)
however, works as expected:
object(Mf_Data)#1 (5) {
["_values":"Mf_Data":private]=>
array(0) {
}
["_children":"Mf_Data":private]=>
array(1) {
[0]=>
array(1) {
["foo"]=>
object(Mf_Data)#2 (5) {
["_values":"Mf_Data":private]=>
array(0) {
}
["_children":"Mf_Data":private]=>
array(1) {
[0]=>
array(1) {
["bar"]=>
object(Mf_Data)#3 (5) {
["_values":"Mf_Data":private]=>
array(1) {
[0]=>
array(1) {
["hello"]=>
string(5) "world"
}
}
["_children":"Mf_Data":private]=>
array(0) {
}
["_parent":"Mf_Data":private]=>
*RECURSION*
["_key":"Mf_Data":private]=>
string(3) "bar"
["_index":"Mf_Data":private]=>
int(0)
}
}
}
["_parent":"Mf_Data":private]=>
*RECURSION*
["_key":"Mf_Data":private]=>
string(3) "foo"
["_index":"Mf_Data":private]=>
int(0)
}
}
}
["_parent":"Mf_Data":private]=>
NULL
["_key":"Mf_Data":private]=>
NULL
["_index":"Mf_Data":private]=>
int(0)
}
Addendum
1)
So this is the toArray()
function I've devised for the Mf_Data
class:
public function toArray()
{
$array = (array) $this;
array_walk_recursive($array, function (&$property) {
if ($property instanceof Mf_Data) {
$property = $property->toArray();
}
});
return $array;
}
However since the Mf_Data
objects also have a reference to their parent (containing) object, this fails with recursion. Works like a charm though when I remove the _parent
reference.
2)
Just to follow up, the final function to transform a complex tree-node object I went with was:
// class name - Mf_Data
// exlcuded properties - $_parent, $_index
public function toArray()
{
$array = get_object_vars($this);
unset($array['_parent'], $array['_index']);
array_walk_recursive($array, function (&$property) {
if (is_object($property) && method_exists($property, 'toArray')) {
$property = $property->toArray();
}
});
return $array;
}
3)
I'm following up again, with a bit cleaner of an implementation. Using interfaces for an instanceof
check seems much cleaner than method_exists()
(however method_exists()
does cross-cut inheritance/implementation).
Using unset()
seemed a bit messy too, and it seems that logic should be refactored into another method. However, this implementation does copy the property array (due to array_diff_key
), so something to consider.
interface ToMapInterface
{
function toMap();
function getToMapProperties();
}
class Node implements ToMapInterface
{
private $index;
private $parent;
private $values = array();
public function toMap()
{
$array = $this->getToMapProperties();
array_walk_recursive($array, function (&$value) {
if ($value instanceof ToMapInterface) {
$value = $value->toMap();
}
});
return $array;
}
public function getToMapProperties()
{
return array_diff_key(get_object_vars($this), array_flip(array(
'index', 'parent'
)));
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
在最简单的情况下,类型提示应该起作用:
In the simplest cases type hinting should work:
编辑:目前是2016年9月24日,PHP 5.4已于2012年3月1日发布,支持已于2015年9月1日结束。尽管如此,这个答案似乎还是获得了支持。如果您仍在使用 PHP < 5.4,您正在造成安全风险并危及您的项目。如果您没有令人信服的理由保持在 <5.4,或者甚至已经使用版本 >= 5.4,不要使用此答案,而只使用 PHP>= 5.4(或者,您知道,最近的一个)并实现 JsonSerialized 接口
您可以定义一个函数,例如名为
getJsonData();
,它将返回一个数组、stdClass
对象或其他具有可见属性的对象。参数而不是私有/受保护的参数,并执行 json_encode($data->getJsonData());。本质上,实现 5.4 中的函数,但手动调用它。像这样的事情是可行的,因为从类内部调用 get_object_vars() ,可以访问私有/受保护的变量:
edit: it's currently 2016-09-24, and PHP 5.4 has been released 2012-03-01, and support has ended 2015-09-01. Still, this answer seems to gain upvotes. If you're still using PHP < 5.4, your are creating a security risk and endagering your project. If you have no compelling reasons to stay at <5.4, or even already use version >= 5.4, do not use this answer, and just use PHP>= 5.4 (or, you know, a recent one) and implement the JsonSerializable interface
You would define a function, for instance named
getJsonData();
, which would return either an array,stdClass
object, or some other object with visible parameters rather then private/protected ones, and do ajson_encode($data->getJsonData());
. In essence, implement the function from 5.4, but call it by hand.Something like this would work, as
get_object_vars()
is called from inside the class, having access to private/protected variables:json_encode()
只会对公共成员变量进行编码。所以如果你想包括私人一旦你必须自己做(正如其他人建议的那样)json_encode()
will only encode public member variables. so if you want to include the private once you have to do it by yourself (as the others suggested)以下代码使用反射来完成这项工作。它假设您有要序列化的属性的 getter
Following code is doing the job using reflection. It assumes you have getters for the properties you want to serialize
只需实现 PHP JsonSerialized 给出的接口即可。
Just implement an Interface given by PHP JsonSerializable.
尝试使用这个,这对我来说效果很好。
Try using this, this worked fine for me.
由于您的对象类型是自定义的,我倾向于同意您的解决方案 - 使用编码方法(例如 JSON 或序列化内容)将其分解为更小的片段,并在另一端有相应的代码来重新构造对象。
Since your object type is custom, I would tend to agree with your solution - break it down into smaller segments using an encoding method (like JSON or serializing the content), and on the other end have corresponding code to re-construct the object.
我的版本:
实现:
JsonUtils: GitHub
My version:
Implementation:
JsonUtils : GitHub
将变量类型
private
更改为public
这很简单且更具可读性。
例如
不工作;
正在工作;
Change to your variable types
private
topublic
This is simple and more readable.
For example
Not Working;
It is Working;
我制作了一个很好的帮助器类,它将带有 get 方法的对象转换为数组。
它不依赖属性,只依赖方法。
所以我有一个以下评论对象,其中包含两个方法:
Review
Comment
我编写的脚本会将其转换为一个数组属性看起来像这样:
来源: PHP 序列化器,它将对象转换为可以编码为 JSON 的数组。
您所要做的就是将 json_encode 包装在输出周围。
有关脚本的一些信息:
I made a nice helper class which converts an object with get methods to an array.
It doesn't rely on properties, just methods.
So i have a the following review object which contain two methods:
Review
Comment
The script I wrote will transform it into an array with properties what looks like this:
Source: PHP Serializer which converts an object to an array that can be encoded to JSON.
All you have to do is wrap json_encode around the output.
Some information about the script:
我花了几个小时来解决同样的问题。
我要转换的对象包含许多其他我不应该触及的定义(API),因此我想出了一个解决方案,我想这可能会很慢,但我将其用于开发目的。
这将任何对象转换为数组
这将任何对象转换为 stdClass
I spent some hours on the same problem.
My object to convert contains many others whose definitions I'm not supposed to touch (API), so I've came up with a solution which could be slow I guess, but I'm using it for development purposes.
This one converts any object to array
This converts any object to stdClass