Reputation: 13037
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
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
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
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
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
Reputation: 61557
From your comment:
I am asking about the magic properties, not the methods. – Vegard Larsen 1 min ago
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
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
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
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
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