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?
$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)
}
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.
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;
}
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'
)));
}
}
In the simplest cases type hinting should work:
$json = json_encode( (array)$object );