json_unescaped_unicode - ¿Deserialización de JSON a PHP, con casting?
leer json php (11)
Supongamos que tengo una clase de usuario con las propiedades ''nombre'' y ''contraseña'', y un método ''guardar''. Al serializar un objeto de esta clase a JSON a través de json_encode, el método se omite correctamente y termino con algo como {''nombre'': ''nombre de prueba'', ''contraseña'': ''prueba de contraseña''}.
Sin embargo, al deserializar a través de json_decode, termino con un objeto StdClass en lugar de un objeto User, lo que tiene sentido, pero esto significa que el objeto carece del método de "guardar". ¿Hay alguna forma de convertir el objeto resultante como usuario o proporcionar alguna sugerencia a json_decode sobre qué tipo de objeto estoy esperando?
A continuación se muestra un ejemplo del uso de estática (es decir, que conoce el tipo de clase en el código) y dinámica (es decir, que solo conoce el tipo de clase en tiempo de ejecución) para deserializar JSON nuevamente en un objeto PHP:
Código
<?php
class Car
{
private $brand;
private $model;
private $year;
public function __construct($brand, $model, $year)
{
$this->brand = $brand;
$this->model = $model;
$this->year = $year;
}
public function toJson()
{
$arr = array(
''brand'' => $this->brand,
''model'' => $this->model,
''year'' => $this->year,
);
return json_encode($arr);
}
public static function fromJson($json)
{
$arr = json_decode($json, true);
return new self(
$arr[''brand''],
$arr[''model''],
$arr[''year'']
);
}
}
// original object
echo ''car1: '';
$car1 = new Car(''Hyundai'', ''Tucson'', 2010);
var_dump($car1);
// serialize
echo ''car1class: '';
$car1class = get_class($car1); // need the class name for the dynamic case below. this would need to be bundled with the JSON to know what kind of class to recreate.
var_dump($car1class);
echo ''car1json: '';
$car1Json = $car1->toJson();
var_dump($car1Json);
// static recreation with direct invocation. can only do this if you know the class name in code.
echo ''car2: '';
$car2 = Car::fromJson($car1Json);
var_dump($car2);
// dynamic recreation with reflection. can do this when you only know the class name at runtime as a string.
echo ''car3: '';
$car3 = (new ReflectionMethod($car1class, ''fromJson''))->invoke(null, $car1Json);
var_dump($car3);
Salida
car1: object(Car)#1 (3) {
["brand":"Car":private]=>
string(7) "Hyundai"
["model":"Car":private]=>
string(6) "Tucson"
["year":"Car":private]=>
int(2010)
}
car1class: string(3) "Car"
car1json: string(48) "{"brand":"Hyundai","model":"Tucson","year":2010}"
car2: object(Car)#2 (3) {
["brand":"Car":private]=>
string(7) "Hyundai"
["model":"Car":private]=>
string(6) "Tucson"
["year":"Car":private]=>
int(2010)
}
car3: object(Car)#4 (3) {
["brand":"Car":private]=>
string(7) "Hyundai"
["model":"Car":private]=>
string(6) "Tucson"
["year":"Car":private]=>
int(2010)
}
Antigua pregunta, pero tal vez alguien lo encuentre útil.
He creado una clase abstracta con funciones estáticas que puede heredar en su objeto para deserializar cualquier JSON en la instancia de clase heredada.
abstract class JsonDeserializer
{
/**
* @param string|array $json
* @return $this
*/
public static function Deserialize($json)
{
$className = get_called_class();
$classInstance = new $className();
if (is_string($json))
$json = json_decode($json);
foreach ($json as $key => $value) {
if (!property_exists($classInstance, $key)) continue;
$classInstance->{$key} = $value;
}
return $classInstance;
}
/**
* @param string $json
* @return $this[]
*/
public static function DeserializeArray($json)
{
$json = json_decode($json);
$items = [];
foreach ($json as $item)
$items[] = self::Deserialize($item);
return $items;
}
}
Lo usa al heredarlo en una clase que tiene los valores que su JSON tendrá:
class MyObject extends JsonDeserializer
{
/** @var string */
public $property1;
/** @var string */
public $property2;
/** @var string */
public $property3;
/** @var array */
public $array1;
}
Ejemplo de uso:
$objectInstance = new MyObject();
$objectInstance->property1 = ''Value 1'';
$objectInstance->property2 = ''Value 2'';
$objectInstance->property3 = ''Value 3'';
$objectInstance->array1 = [''Key 1'' => ''Value 1'', ''Key 2'' => ''Value 2''];
$jsonSerialized = json_encode($objectInstance);
$deserializedInstance = MyObject::Deserialize($jsonSerialized);
Puede usar el método ::DeserializeArray
si su JSON contiene una matriz de su objeto de destino.
Here muestra ejecutable.
Creo que la mejor manera de manejar esto sería a través del constructor, ya sea directamente o a través de una fábrica:
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));
}
}
Debo decir que estoy un poco consternado de que esto no sea solo una funcionalidad estándar, lista para usar, de alguna biblioteca, si no la misma JSON. ¿Por qué no querrías tener objetos esencialmente similares en ambos lados? (Hasta donde JSON te lo permita, de todos modos)
¿Me estoy perdiendo de algo? ¿Hay alguna biblioteca que haga esto? (AFAICT, ninguno de [thrift, protocol buffers, avro] en realidad tiene API para javascript. Para mi problema, estoy más interesado en JS <-> PHP, un poco también en JS <-> python).
Echa un vistazo a esta clase que escribí:
https://github.com/mindplay-dk/jsonfreeze/blob/master/mindplay/jsonfreeze/JsonSerializer.php
Reserva una propiedad de objeto JSON llamada ''#type'' para almacenar el nombre de clase, y tiene algunas limitaciones que se describen aquí:
Para responder a su pregunta directa, no, no hay que hacer esto con json_encode / json_decode. JSON fue diseñado y especificado para ser un formato para codificar información y no para serializar objetos. La función de PHP no va más allá de eso.
Si está interesado en recrear objetos desde JSON, una posible solución es un método estático en todos los objetos de su jerarquía que acepte un stdClass / string y rellene las variables que se ven así.
//semi pseudo code, not tested
static public function createFromJson($json){
//if you pass in a string, decode it to an object
$json = is_string($json) ? json_decode($json) : $json;
foreach($json as $key=>$value){
$object = new self();
if(is_object($value)){
$object->{$key} = parent::createFromJson($json);
}
else{
$object->{$key} = $value;
}
}
return $object;
}
No probé eso, pero espero que se haga realidad la idea. Lo ideal es que todos sus objetos se extiendan desde algún objeto base (generalmente denominado "objeto de clase") para que pueda agregar este código en un solo lugar.
Poco tarde, pero otra opción es usar el serializador de Symfony para deserializar xml, json, lo que sea para Object.
Aquí está la documentación: http://symfony.com/doc/current/components/serializer.html#deserializing-in-an-existing-object
Podría crear una clase de fábrica de algún tipo:
function create(array $data)
{
$user = new User();
foreach($data as $k => $v) {
$user->$k = $v;
}
return $user;
}
No es como la solución que deseaba, pero hace su trabajo.
Respuesta corta: No (no que yo sepa de *)
Respuesta larga: json_encode solo serializará variables públicas. Como puede ver por la especificación JSON , no hay ningún tipo de datos de "función". Estas son las dos razones por las que sus métodos no se serializan en su objeto JSON.
Ryan Graham tiene razón: la única forma de recrear estos objetos como instancias que no son stdClass es recrearlos después de la deserialización.
Ejemplo
<?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();
Alternativamente, a menos que realmente necesite los datos como JSON, puede usar la serialización normal y aprovechar los ganchos __sleep () y __wakeup () para lograr una personalización adicional.
*
En una pregunta anterior de mi propia opinión , se sugirió que podría implementar algunas de las interfaces SPL para personalizar la entrada / salida de json_encode (), pero mis pruebas revelaron que se trata de persecuciones de animales salvajes.
Soy consciente de que JSON no admite la serialización de funciones, lo cual es perfectamente aceptable e incluso deseado. Mis clases se utilizan actualmente como objetos de valor en la comunicación con JavaScript, y las funciones no tendrían ningún significado (y las funciones de serialización normales no son utilizables).
Sin embargo, a medida que aumenta la funcionalidad perteneciente a estas clases, encapsular sus funciones de utilidad (como el guardado () de un usuario en este caso) dentro de la clase real tiene sentido para mí. Sin embargo, esto significa que ya no son objetos de valor estricto, y ahí es donde me encuentro con el problema mencionado anteriormente.
Una idea que tenía tenía el nombre de clase especificado dentro de la cadena JSON, y probablemente terminaría así:
$string = ''{"name": "testUser", "password": "testPassword", "class": "User"}'';
$object = json_decode ($string);
$user = ($user->class) $object;
Y lo contrario sería configurar la propiedad de clase durante / después de json_encode. Lo sé, un poco complicado, pero solo estoy tratando de mantener el código relacionado. Probablemente termine sacando mis funciones de utilidad de las clases nuevamente; El enfoque del constructor modificado parece un poco opaco y tiene problemas con los objetos anidados.
Sin embargo, aprecio esto y cualquier comentario futuro.
Tal vez el patrón de hydration
puede ser de ayuda.
Básicamente, crea una instancia de un nuevo objeto vacío ( new User()
) y luego completa las propiedades con los valores del objeto StdClass
. Por ejemplo, podría tener un método de hydrate
en User
.
Si es posible en su caso, puede hacer que el constructor
del User
acepte un parámetro opcional de tipo StdClass
y tome los valores en la instanciación.