8

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.

1
  • FWIW: a "mysqli result-set" (the value returned by a mysqli query) is one example of such an object. json_encode sees null for the properties, though var_dump and print_r are able to correctly dump their values. Commented Aug 25, 2020 at 22:06

8 Answers 8

29

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.

Sign up to request clarification or add additional context in comments.

Comments

5

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

Comments

4

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.

5 Comments

So, the fact that the property "doesn't exist" in the object is of no interest to me. Please see the revised question.
Well, if the property doesn't exist, it isn't going to be put into JSON.
And there is NO way to alert json_encode to this? Implementing IteratorAggregate alerts foreach of the properties; why doesn't json_encode pick this up?
To me, this answer misses the mark. var_dump and print_r do exactly what is asked for, in my experience [at least they work for a mysqli result-set, which has similar behavior to what is described by OP]. So whatever the correct terminology is, the question is how to "see" those "key-value" pairs "known by" an object, in the same way that dumping the object does.
... actually, the mysqli result-set is a different situation. For that, json_encode sees that the property (names) exist - it just uses code that (IMHO) short-circuits the usual property-retrieval mechanism, hence fails to find the value of the property, encoding null. On further reflection, this answer is correct: for OP's situation, it isn't json-encodes responsibility to somehow find "properties" whose names are themselves dynamic. Unfortunately, it is too late for me to remove my downvote. My apologies.
3

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.

1 Comment

Fortunately, the thread is no longer downrated: the question has attracted 10 upvotes to 3 downvotes.
1

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();

6 Comments

What happens when you try to json_encode an array of these objects? json_encode won't know to call getJson(). This was mentioned in the question, as well.
The idea is not not use json_encode .... it is to call the getJSON on the object instead.
Look at the exmaple after the class, as Chacha102 says, you use the getJson method I've added to the class and this then does know which properties exist because it can access the class internals.
I have an array of these objects. Calling json_encode on every single object in the array is not exactly a clean way of solving this.
Well, with the structure you have, it seems to be the only way to do it. you may have been better off not using the magic __get functions and just having a stdClass objects that you attach public properies to
|
0

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.

1 Comment

There are no magic properties, just magic methods that get called when you try to access non-declared properties.
0

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)

Comments

0

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

1 Comment

Dynamic properties as listed in that article works with any object, and does not allow the properties to be calculated on access, as they have to be assigned first.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.