105

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

}
Dan Lugg
  • 20,192
  • 19
  • 110
  • 174
  • 4
    +1 Nice question, didn't know this feature yet. – takeshin Jul 26 '11 at 21:44
  • @takeshin - Yeop, edit date on the doc page is 4 days ago. I'm glad to see it! – Dan Lugg Jul 26 '11 at 21:56
  • 2
    For reference to others looking at this, json_encode can handle objects just fine. However, it only encodes public-members of that object. So if you have protected or private class variables, then you need either one of the posted methods, or JsonSerializable. – Matthew Herbst Dec 22 '14 at 22:01
  • @MatthewHerbst Certainly. Old question is old now, and < 5.4 isn't really an option anymore anyway (or at least shouldn't be) Definitely `JsonSerializable` – Dan Lugg Dec 23 '14 at 02:25

11 Answers11

94

In the simplest cases type hinting should work:

$json = json_encode( (array)$object );
takeshin
  • 49,108
  • 32
  • 120
  • 164
47

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;
}
Wrikken
  • 69,272
  • 8
  • 97
  • 136
  • 2
    Thanks @Wrikken - Is there any shortcut for reducing an object, objects contained therein (*all members regardless of visibility or type*) to an associative array, or typecasting it to `stdClass`? I'm thinking in the direction of *Reflection*, but if not, I'll just figure out something to recursively perform it. – Dan Lugg Jul 26 '11 at 21:28
  • Reflection would be the long way. As you are _inside_ the class in your `getJsonData()` function, you could just call `get_object_vars()`, and loop through that result looking for more objects. – Wrikken Jul 26 '11 at 21:38
  • I've nearly gotten it sorted out; the issue now is recursion. Each object has a `_parent` property so the tree can be traversed to the root. See my edit for an update; perhaps I should ask another question as this issue is now abstracted from my original. – Dan Lugg Jul 26 '11 at 21:51
  • A simple `unset($array['_parent']);` before the walk should do the trick. – Wrikken Jul 26 '11 at 22:14
  • Awesome, thanks @Wrikken - I was starting to try complicated equality tests, passing a context object `$parent` as user-data to `array_walk_recursive()`. Simple is beautiful! Also, its `$array["\0class\0property"]` because of null-byte pollution because I was using casting. I think I'll switch to `get_object_vars()`. – Dan Lugg Jul 26 '11 at 22:32
21

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)

Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
jfried
  • 505
  • 2
  • 5
12

Following code is doing the job using reflection. It assumes you have getters for the properties you want to serialize

    <?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;
    }  
} 
Robusto
  • 31,447
  • 8
  • 56
  • 77
Danny Yeshurun
  • 407
  • 6
  • 10
7

Just implement an Interface given by PHP JsonSerializable.

fabpico
  • 2,628
  • 4
  • 26
  • 43
webcodecs
  • 217
  • 2
  • 9
2

My version:

json_encode(self::toArray($ob))

Implementation:

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

John Tribe
  • 1,407
  • 14
  • 26
2

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.

barfoon
  • 27,481
  • 26
  • 92
  • 138
1

Try using this, this worked fine for me.

json_encode(unserialize(serialize($array)));
Navaneeth Mohan
  • 557
  • 1
  • 5
  • 9
1

Change to your variable types private to public

This is simple and more readable.

For example

Not Working;

class A{
   private $var1="valuevar1";
   private $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}

It is Working;

class A{
   public $var1="valuevar1";
   public $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}
Ferhat KOÇER
  • 3,890
  • 1
  • 26
  • 26
0

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

  • getAmountReviews : int
  • getReviews : array of comments

Comment

  • getSubject
  • getDescription

The script I wrote will transform it into an array with properties what looks like this:

    {
      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."
    }
]}

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:

  • Only methods which starts with get are added
  • Private methods are ignored
  • Constructor is ignored
  • Capital characters in the method name will be replaced with an underscore and lowercased character
Jamie
  • 3,031
  • 5
  • 36
  • 59
-7

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

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

This converts any object to 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;
}
  • There is another fine and accurately answer, already accepted. Does your answer adds something radically different, more efficient or compact? I guess not – Yaroslav Oct 04 '12 at 06:38
  • I'm going to be honest; I don't think this answers the question, at all. – Dan Lugg Aug 20 '13 at 14:00
  • 5
    It's been about 6 months; I've periodically returned here due to upvotes, and to make some edits for future visitors; I **still** have no idea what the hell this is supposed to do. – Dan Lugg Feb 04 '14 at 18:05
  • `unlink($thisAnswer);` – Dan Lugg May 28 '15 at 15:33
  • people tend to downvote what they don't understand. It might not be per say an exact solution but it is something to look into. In such case you ask for clarifications before downvotes. – Gimali Jun 15 '16 at 20:22
  • 1
    `inline php strings`, `eval`, `shell_exec(php)`... c-c-combo. – vp_arth Aug 17 '16 at 19:38