将PHP对象序列化为JSON
所以当我偶然发现新的JsonSerializable接口时,我正在php.net中寻找有关将PHP对象序列化为JSON的信息。 这只是PHP> = 5.4 ,而我正在5.3.x环境中运行。
这种function如何实现PHP <5.4 ?
我还没有用JSON工作太多,但我试图在应用程序中支持API层,并将数据对象( 否则将被发送到视图 )转储到JSON将是完美的。
如果我试图直接序列化对象,它将返回一个空的JSONstring; 这是因为我认为json_encode()
不知道与对象做什么。 我应该recursion减less对象到一个数组,然后编码?
例
$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
类devise的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
对象也具有对其父( 含 )对象的引用,因此这会失败,并发生recursion。 虽然当我删除_parent
引用时,像魅力工作。
2)
为了跟进,我转换的复杂树节点对象的最终function是:
// 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()
不交叉inheritance/实现 )。
使用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' ))); } }
编辑 :目前是2016-09-24,并且PHP 5.4已经在2012-03-01发布,支持已经在 2015-09-01 结束 。 不过,这个答案似乎得到了提升。 如果您仍然使用PHP <5.4,那么您正在创build安全风险并终止您的项目 。 如果你没有足够的理由保持<5.4,甚至已经使用版本> = 5.4, 不要使用这个答案 ,只要使用PHP> = 5.4(或者,你知道最近的一个),并实现JsonSerializable接口
你可以定义一个函数,比如名为getJsonData();
,这将返回一个数组, stdClass
对象或一些其他对象与可见参数,而不是私人/受保护的,并做一个json_encode($data->getJsonData());
。 本质上,从5.4开始实现这个function,但是手工调用它。
像这样的东西可以工作,因为get_object_vars()
从类内部调用,可以访问私有/受保护的variables:
function getJsonData(){ $var = get_object_vars($this); foreach ($var as &$value) { if (is_object($value) && method_exists($value,'getJsonData')) { $value = $value->getJsonData(); } } return $var; }
在最简单的情况下,types暗示应该工作:
$json = json_encode( (array)$object );
json_encode()
只会编码公共成员variables。 所以如果你想包括私人一旦你必须自己做(如其他人所build议的)
以下代码使用reflection来完成这项工作。 它假定你有你想要序列化的属性的getter
<?php /** * Serialize a simple PHP object into json * Should be used for POPO that has getter methods for the relevant properties to serialize * A property can be simple or by itself another POPO object * * Class CleanJsonSerializer */ class CleanJsonSerializer { /** * Local cache of a property getters per class - optimize reflection code if the same object appears several times * @var array */ private $classPropertyGetters = array(); /** * @param mixed $object * @return string|false */ public function serialize($object) { return json_encode($this->serializeInternal($object)); } /** * @param $object * @return array */ private function serializeInternal($object) { if (is_array($object)) { $result = $this->serializeArray($object); } elseif (is_object($object)) { $result = $this->serializeObject($object); } else { $result = $object; } return $result; } /** * @param $object * @return \ReflectionClass */ private function getClassPropertyGetters($object) { $className = get_class($object); if (!isset($this->classPropertyGetters[$className])) { $reflector = new \ReflectionClass($className); $properties = $reflector->getProperties(); $getters = array(); foreach ($properties as $property) { $name = $property->getName(); $getter = "get" . ucfirst($name); try { $reflector->getMethod($getter); $getters[$name] = $getter; } catch (\Exception $e) { // if no getter for a specific property - ignore it } } $this->classPropertyGetters[$className] = $getters; } return $this->classPropertyGetters[$className]; } /** * @param $object * @return array */ private function serializeObject($object) { $properties = $this->getClassPropertyGetters($object); $data = array(); foreach ($properties as $name => $property) { $data[$name] = $this->serializeInternal($object->$property()); } return $data; } /** * @param $array * @return array */ private function serializeArray($array) { $result = array(); foreach ($array as $key => $value) { $result[$key] = $this->serializeInternal($value); } return $result; } }
只需实现一个由PHP JsonSerializable给出的接口。
由于您的对象types是自定义的,我倾向于同意您的解决scheme – 使用编码方法(如JSON或序列化内容)将其分解成更小的片段,另一端具有相应的代码来重构对象。
我的版本:
json_encode(self::toArray($ob))
执行:
private static function toArray($object) { $reflectionClass = new \ReflectionClass($object); $properties = $reflectionClass->getProperties(); $array = []; foreach ($properties as $property) { $property->setAccessible(true); $value = $property->getValue($object); if (is_object($value)) { $array[$property->getName()] = self::toArray($value); } else { $array[$property->getName()] = $value; } } return $array; }
JsonUtils: GitHub
我做了一个很好的帮助类,它将get方法转换成一个数组。 它不依赖于属性,只是方法。
所以我有一个包含两个方法的以下审查对象:
评论
- getAmountReviews:int
- getReviews:评论数组
评论
- getSubject
- getDescription
我写的脚本将它转换成一个数组,其属性如下所示:
{ amount_reviews: 21, reviews: [ { subject: "In een woord top 1!", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor." }, { subject: "En een zwembad 2!", description: "Maecenas et aliquet mi, a interdum mauris. Donec in egestas sem. Sed feugiat commodo maximus. Pellentesque porta consectetur commodo. Duis at finibus urna." }, { subject: "In een woord top 3!", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor." }, { subject: "En een zwembad 4!", description: "Maecenas et aliquet mi, a interdum mauris. Donec in egestas sem. Sed feugiat commodo maximus. Pellentesque porta consectetur commodo. Duis at finibus urna." }, { subject: "In een woord top 5!", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor." } ]}
来源: PHP的串行器,它将一个对象转换为一个可以编码为JSON的数组。
你所要做的就是将json_encode包装在输出中。
有关脚本的一些信息:
- 只有以get开头的方法被添加
- 私有方法被忽略
- 构造函数被忽略
- 方法名中的大写字母将被replace为下划线和小写字符
我花了几个小时来解决同样的问题。 我的转换对象包含许多其他的定义,我不应该触摸(API),所以我想出了一个解决scheme,可能会慢我猜,但我用它的开发目的。
这个将任何对象转换为数组
function objToArr($o) { $s = '<?php class base { public static function __set_state($array) { return $array; } } function __autoload($class) { eval("class $class extends base {}"); } $a = '.var_export($o,true).'; var_export($a); '; $f = './tmp_'.uniqid().'.php'; file_put_contents($f,$s); chmod($f,0755); $r = eval('return '.shell_exec('php -f '.$f).';'); unlink($f); return $r; }
这将任何对象转换为stdClass
class base { public static function __set_state($array) { return (object)$array; } } function objToStd($o) { $s = '<?php class base { public static function __set_state($array) { $o = new self; foreach($array as $k => $v) $o->$k = $v; return $o; } } function __autoload($class) { eval("class $class extends base {}"); } $a = '.var_export($o,true).'; var_export($a); '; $f = './tmp_'.uniqid().'.php'; file_put_contents($f,$s); chmod($f,0755); $r = eval('return '.shell_exec('php -f '.$f).';'); unlink($f); return $r; }