phpobjectjsonobject-serialization

Serializing PHP object to JSON


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'
        )));
    }

}

Solution


  • 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 a json_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:

    function getJsonData(){
        $var = get_object_vars($this);
        foreach ($var as &$value) {
            if (is_object($value) && method_exists($value,'getJsonData')) {
                $value = $value->getJsonData();
            }
        }
        return $var;
    }