PHP: How do you convert an object into an array?
The cast (array) $object is the answer you find everywhere. With a stdClass
it does work. With a class of your own it gives you keys you can no longer
address.
The simple case
A stdClass, as returned by json_decode(), can be cast directly:
<?php
$obj = new stdClass();
$obj->host = 'localhost';
$obj->port = 3306;
print_r((array) $obj);
?>
Array
(
[host] => localhost
[port] => 3306
)
As long as all properties are public, that is the end of it.
With your own classes it gets ugly
Now the same thing with a real class:
<?php
class Server {
public string $host = 'localhost';
protected int $port = 3306;
private string $password = 'secret';
}
$arr = (array) new Server();
?>
The array has three entries. They are just not named the way you would think:
host (length 4)
\0*\0port (length 7)
\0Server\0password (length 16)
Those are NUL bytes in the keys. The scheme is:
protected→\0+*+\0+ nameprivate→\0+ class name +\0+ name
The practical consequence:
isset($arr['port']) : false
isset($arr["\0*\0port"]) : true
So you can only get at the value if you rebuild the NUL-byte key exactly. And for that you need to know which class declared the property — with inheritance that is not necessarily the object’s own class.
The display is particularly annoying. print_r() renders the key like this:
[port:protected] => 3306
That looks as if the key were port, with a piece of extra information
appended. It is not. print_r() interprets the NUL-byte scheme for you and
turns it into something readable. Anyone who then reaches for $arr['port']
will be searching for a while.
Incidentally, the cast is not the only place where visibility interferes.
json_encode() only takes the public properties along:
json_encode($server) : {"host":"localhost"}
serialize() does take all of them, but uses the same NUL-byte names for it:
O:6:"Server":3:{s:4:"host";...s:7:"\0*\0port";...s:16:"\0Server\0password";...}
The s:7: and s:16: are the length markers — the same numbers as above.
get_object_vars() — and where you call it
get_object_vars() returns the properties with clean keys. Which ones you see,
however, depends on where you call the function from:
from outside : ["host"]
from inside : ["host","port","password"]
From outside the class you only see public. Inside the class you see
everything — and get normal keys without NUL bytes.
That points the way to the clean solution: you put the conversion inside the class.
<?php
class Server {
public string $host = 'localhost';
protected int $port = 3306;
private string $password = 'secret';
public function toArray(): array {
return get_object_vars($this);
}
}
?>
It also has the advantage that you decide what goes out. The password is probably not something you want in that array at all.
The cast is not recursive
The next stumbling block. With nested objects the cast only converts the topmost level:
(array) $outer : ['inner'] is stdClass <- still an object
via JSON : ['inner'] is array
That is why you find this detour recommended everywhere:
<?php
$arr = json_decode(json_encode($obj), true);
?>
It does in fact work recursively. json_decode() with true as its second
parameter returns arrays instead of stdClass throughout:
json_decode($json) : stdClass
->db is : stdClass
json_decode($json, true) : array
['db'] is : array
But the JSON detour costs you something
Namely type information. The round trip through JSON only knows the types JSON knows:
1.0 becomes : 1 (int)
PHP_INT_MAX survives : true
DateTimeImmutable : {"date":"2026-08-28 00:00:00.000000","timezone_type":3,"timezone":"UTC"}
The first line is the one you meet in practice. A price of 19.0 comes back as
int 19, because JSON does not distinguish between 19 and 19.0. As long as
you only do arithmetic with it, that goes fine. With a === comparison or a
function signature typed float it shows up.
And every object that is more than a plain data container loses its type. The
DateTimeImmutable turns into an array of three fields. That cannot be
converted back.
One thing that does work, by the way: PHP_INT_MAX survives the detour
correctly. That is occasionally claimed otherwise.
What I recommend
stdClassfromjson_decode(): passtrueas the second parameter right away, then there is nothing to convert.- Your own class: write a
toArray()method. Ten lines, but predictable. - A foreign class you cannot change:
get_object_vars()if the public properties are enough for you. Otherwise the Reflection API. - The JSON detour only if you do not care about types.
Summary
(array)only works smoothly forstdClassor purely public classes.protectedbecomes"\0*\0name",privatebecomes"\0Class\0name".print_r()shows[port:protected]— that is not the key name.get_object_vars()sees everything from inside, onlypublicfrom outside.- The cast is not recursive,
json_decode(..., true)is. - The JSON detour turns
1.0into anintand objects into arrays.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).