Vegard Larsen
Vegard Larsen

Reputation: 13037

json_encode on class with magic properties

I am trying to json_encode an array of objects who all have magic properties using __get and __set. json_encode completely ignores these, resulting in an array of empty objects (all the normal properties are private or protected).

So, imagine this class:

class Foo
{
    public function __get($sProperty)
    {
        if ($sProperty == 'foo')
        {
            return 'bar!';
        }
        return null;
    }
}

$object = new Foo();
echo $object->foo; // echoes "foo"
echo $object->bar; // warning
echo json_encode($object); // "{}"

I've tried implementing IteratorAggregate and Serializable for the class, but json_encode still doesn't see my magic properties. Since I am trying to encode an array of these objects, an AsJSON()-method on the class won't work either.

Update! It seems the question is easy to misunderstand. How can I tell json_encode which "magic properties" exist? IteratorAggregate didn't work.

BTW: The term from the PHP documentation is "dynamic entities". Whether or not the magic properties actually exist is arguing about semantics.

Upvotes: 8

Views: 11098

Answers (8)

Levi Morrison
Levi Morrison

Reputation: 19552

PHP 5.4 has an interface called JsonSerializable. It has one method, jsonSerialize which will return the data to be encoded. This problem would be easily fixed by implementing it.

Upvotes: 29

dj-marty
dj-marty

Reputation: 1

i have found the following that worked for me creating classes with dynamic properties and outputing them with json_encode. Maybe it helps other people to.

http://krisjordan.com/dynamic-properties-in-php-with-stdclass

Upvotes: 0

Vincent Polite
Vincent Polite

Reputation: 31

This is an incredibly old thread, but to not confuse other posters who are interested in how magic methods are used to get/set properties in PHP and how that affects JSON encoding, here is my understanding.

Including __get and __set methods in a class give you an interface to deal with dynamically named properties for a given class. Most people define an internal associative array that does the housekeeping.

The reason your foo/bar example doesn't show up in JSON encoding is because you are simply creating an association that returns a value. It's not setting a property in the object itself to that value.

If you did something like:

public function set($name, $value) { $this->data[$name] = $value; }

Then if you called :

$foo = new ObjectClass; $foo->set('foo','bar');

now there is a element in that array $data['foo'] = 'bar' if you json_encode the object, then that relationship will be represented.

json_encode does not encode methods, only properties. There's no way around it. For your code, the functional equivalent of your magic __get, would be to include in the constructor a call to set, that "hardwires" the value of the property named "foo".

Ultimately, I don't know what specifics you are wrestling with as you didn't provide much in the way of detail. However, json_encoding a object or an array will simply give you a list of properties that are available. If you have to pass through an interpreting function and are relying on that somehow, that's a different problem that I unfortunately have no answer for.

I hope this helps even though it is part of a downrated thread, which ironically, IMO, contains the solution.

Upvotes: 3

VolkerK
VolkerK

Reputation: 96159

json_encode() doesn't "asks" the object for any interface. It directly fetches the HashTable pointer that represents the properties of an object by calling obj->get_properties(). It then iterates (again directly, no interface such as Traversable, Iterator etc. is used) over this HashTable and processes the elements that are marked as public. see static void json_encode_array() in ext/json/json.c
That makes it impossible to have a property to show up in the result of json_encode() but not to be accessible as $obj->propname.

edit: I haven't tested it much and forget about "high performance" but you might want to start with

interface EncoderData {
  public function getData();
}

function json_encode_ex_as_array(array $v) {
  for($i=0; $i<count($v); $i++) {
    if ( !isset($v[$i]) ) {
      return false;
    }
  }
  return true;
}

define('JSON_ENCODE_EX_SCALAR', 0);
define('JSON_ENCODE_EX_ARRAY', 1);
define('JSON_ENCODE_EX_OBJECT', 2);
define('JSON_ENCODE_EX_EncoderDataObject', 3);

function json_encode_ex($v) {
  if ( is_object($v) ) {
    $type = is_a($v, 'EncoderData') ? JSON_ENCODE_EX_EncoderDataObject : JSON_ENCODE_EX_OBJECT;
  }
  else if ( is_array($v) ) {
    $type = json_encode_ex_as_array($v) ? JSON_ENCODE_EX_ARRAY : JSON_ENCODE_EX_OBJECT;
  }
  else {
    $type = JSON_ENCODE_EX_SCALAR;
  }

  switch($type) {
    case JSON_ENCODE_EX_ARRAY: // array [...]
      foreach($v as $value) {
        $rv[] = json_encode_ex($value);
      }
      $rv = '[' . join(',', $rv) . ']';
      break;
    case JSON_ENCODE_EX_OBJECT: // object { .... }
      $rv = array();
      foreach($v as $key=>$value) {
        $rv[] = json_encode((string)$key) . ':' . json_encode_ex($value);
      }
      $rv = '{' . join(',', $rv) .'}';
      break;
    case JSON_ENCODE_EX_EncoderDataObject:
      $rv = json_encode_ex($v->getData());
      break;
    default:
      $rv = json_encode($v);
  }
  return $rv;
}

class Foo implements EncoderData {
  protected $name;
  protected $child;

  public function __construct($name, $child) {
    $this->name = $name;
    $this->child = $child;

  }
  public function getData() {
    return array('foo'=>'bar!', 'name'=>$this->name, 'child'=>$this->child);
  }
}


$data = array();
for($i=0; $i<10; $i++) {
  $root = null;
  foreach( range('a','d') as $name ) {
    $root = new Foo($name, $root);
  }
  $data[] = 'iteration '.$i;
  $data[] = $root;
  $root = new StdClass;
  $root->i = $i;
  $data[] = $root;
}
$json = json_encode_ex($data);
echo $json, "\n\n\n";
$data = json_decode($json);
var_dump($data);

There is at least one flaw: It doesn't handle recursion, e.g.

$obj = new StdClass;
$obj->x = new StdClass;
$obj->x->y = $obj;
echo json_encode($obj); // warning: recursion detected...
echo json_encode_ex($obj); // this one runs until it hits the memory limit

Upvotes: 5

Tyler Carter
Tyler Carter

Reputation: 61557

From your comment:

I am asking about the magic properties, not the methods. – Vegard Larsen 1 min ago

 

There is no such thing as a magic property.

 

All you have right now is a magic method that gets called when you try to access a non-visible property.

Let's say it again

 

$obj->foo does not exist

 

The way a magic method is implemented is not a concern of PHP, and it can't magically know that you are using your magic method to 'magically' make it look like there is $obj->foo.

If a property does not exist, it will not be put into the object when you json_encode it.

Furthermore, even if json_encode knew that __get was active, it wouldn't know what value to use to call it.

Upvotes: 4

Simon
Simon

Reputation: 37978

I'd create a method on the object to return the internal array.

class Foo
{
    private $prop = array();

    public function __get($sProperty)
    {
       return $this->prop[$sProperty];
    }

    public function __set($sProperty, $value)
    {
       $this->prop[$sProperty] = $value;
    }

    public function getJson(){
        return json_encode($this->prop);
    }
}



$f = new Foo();
$f->foo = 'bar';
$json = $f->getJson();

Upvotes: 1

just somebody
just somebody

Reputation: 19247

How could it know your properties?

$object = new Foo();
echo $object->foo; // how do you know to access foo and not oof?
                   // how do you expect json_encode to know to access foo?

magic methods are syntactic sugar, and mostly fire back when you use them. this is one such case.

echo json_encode($object); // "{}"

of course it's empty, $object has zero public properties, just a magic method and the ensuing syntactic sugar (turned sour)

Upvotes: 0

meouw
meouw

Reputation: 42140

This is the correct behavior
JSON is only able to contain data not methods - it is meant to be language independent so encoding object methods would not make sense.

Upvotes: 0

Related Questions