Suppose I have a User class with \'name\' and \'password\' properties, and a \'save\' method. When serializing an object of this class to JSON via json_encode, the method is pro
I think the best way to handle this would be via the constructor, either directly or via a factory:
class User
{
public $username;
public $nestedObj; //another class that has a constructor for handling json
...
// This could be make private if the factories below are used exclusively
// and then make more sane constructors like:
// __construct($username, $password)
public function __construct($mixed)
{
if (is_object($mixed)) {
if (isset($mixed->username))
$this->username = $mixed->username;
if (isset($mixed->nestedObj) && is_object($mixed->nestedObj))
$this->nestedObj = new NestedObject($mixed->nestedObj);
...
} else if (is_array($mixed)) {
if (isset($mixed['username']))
$this->username = $mixed['username'];
if (isset($mixed['nestedObj']) && is_array($mixed['nestedObj']))
$this->nestedObj = new NestedObj($mixed['nestedObj']);
...
}
}
...
public static fromJSON_by_obj($json)
{
return new self(json_decode($json));
}
public static fromJSON_by_ary($json)
{
return new self(json_decode($json, TRUE));
}
}
You could create a FactoryClass of some sort:
function create(array $data)
{
$user = new User();
foreach($data as $k => $v) {
$user->$k = $v;
}
return $user;
}
It's not like the solution you wanted, but it gets your job done.
Maybe the hydration
pattern can be of help.
Basically you instantiate an new empty object (new User()
) and then you fill in the properties with values from the StdClass
object. For example you could have a hydrate
method in User
.
If possible in your case, you can make the User
constructor
accept an optional parameter of type StdClass
and take the values at instantiation.
I'm aware that JSON doesn't support the serialization of functions, which is perfectly acceptable, and even desired. My classes are currently used as value objects in communicating with JavaScript, and functions would hold no meaning (and the regular serialization functions aren't usable).
However, as the functionality pertaining to these classes increases, encapsulating their utility functions (such as a User's save() in this case) inside the actual class makes sense to me. This does mean they're no longer strictly value objects though, and that's where I run into my aforementioned problem.
An idea I had would have the class name specified inside the JSON string, and would probably end up like this:
$string = '{"name": "testUser", "password": "testPassword", "class": "User"}';
$object = json_decode ($string);
$user = ($user->class) $object;
And the reverse would be setting the class property during/after json_encode. I know, a bit convoluted, but I'm just trying to keep related code together. I'll probably end up taking my utility functions out of the classes again; the modified constructor approach seems a bit opaque and runs into trouble with nested objects.
I do appreciate this and any future feedback, however.
Old question, new answer.
What about creating your own interface to match JsonSerializable? ;)
/**
* Interface JsonUnseriablizable
*/
interface JsonUnseriablizable {
/**
* @param array $json
*/
public function jsonUnserialize(array $json);
}
example:
/**
* Class Person
*/
class Person implements JsonUnseriablizable {
public $name;
public $dateOfBirth;
/**
* @param string $json
*/
public function jsonUnserialize(array $json)
{
$this->name = $json['name'] ?? $this->name;
$this->dateOfBirth = $json['date_of_birth'] ?? $this->dateOfBirth;
}
}
$json = '{"name":"Bob","date_of_birth":"1970-01-01"}';
$person = new Person();
if($person instanceof JsonUnseriablizable){
$person->jsonUnserialize(json_decode($json, true, 512, JSON_THROW_ON_ERROR));
}
var_dump($person->name);
Short answer: No (not that I know of*)
Long answer: json_encode will only serialize public variables. As you can see per the JSON spec, there is no "function" datatype. These are both reasons why your methods aren't serialized into your JSON object.
Ryan Graham is right - the only way to re-create these objects as non-stdClass instances is to re-create them post-deserialization.
Example
<?php
class Person
{
public $firstName;
public $lastName;
public function __construct( $firstName, $lastName )
{
$this->firstName = $firstName;
$this->lastName = $lastName;
}
public static function createFromJson( $jsonString )
{
$object = json_decode( $jsonString );
return new self( $object->firstName, $object->lastName );
}
public function getName()
{
return $this->firstName . ' ' . $this->lastName;
}
}
$p = new Person( 'Peter', 'Bailey' );
$jsonPerson = json_encode( $p );
$reconstructedPerson = Person::createFromJson( $jsonPerson );
echo $reconstructedPerson->getName();
Alternatively, unless you really need the data as JSON, you can just use normal serialization and leverage the __sleep() and __wakeup() hooks to achieve additional customization.
*
In a previous question of my own it was suggested that you could implement some of the SPL interfaces to customize the input/output of json_encode() but my tests revealed those to be wild goose chases.