2009-03-18 27 views
17

Supongamos que tengo una clase de usuario con propiedades 'nombre' y 'contraseña' y un método 'guardar'. Al serializar un objeto de esta clase en JSON a través de json_encode, el método se omite correctamente y termino con algo como {'name': 'testName', 'password': 'testPassword'}.Deserialización de JSON a PHP, ¿con casting?

Sin embargo, al deserializar a través de json_decode, termino con un objeto StdClass en lugar de un objeto Usuario, lo que tiene sentido, pero esto significa que el objeto carece del método 'guardar'. ¿Hay alguna forma de convertir el objeto resultante como un usuario, o para proporcionar alguna pista a json_decode en cuanto a qué tipo de objeto estoy esperando?

Respuesta

9

Creo que la mejor manera de manejar esto sería a través del constructor, ya sea directamente oa 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)); 
    } 
} 
+0

Exactamente. JSON no tiene forma de codificar qué tipo de objeto era el original. – Powerlord

9

Respuesta corta: No (no que yo sepa *)

Respuesta larga : json_encode solo serializará las variables públicas. Como puede ver por el JSON spec, no hay un tipo de datos "función". Estas son las dos razones por las cuales sus métodos no están serializados en su objeto JSON.

Ryan Graham tiene razón: la única forma de volver a crear estos objetos como instancias que no sean stdClass es volver a crearlos 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(); 

alternativa, a menos que realmente necesita los datos como JSON, sólo puede utilizar y aprovechar el normal serialization__sleep() and __wakeup() hooks para lograr una personalización adicional.

* En a previous question of my own se sugirió que se podía poner en práctica algunas de las interfaces de SPL para personalizar la entrada/salida de json_encode() pero mis pruebas revelaron que aquellos búsquedas inútiles.

1

Soy consciente de que JSON no admite la serialización de funciones, lo cual es perfectamente aceptable, e incluso deseado. Actualmente, mis clases se utilizan como objetos de valor para comunicarse con JavaScript, y las funciones no tienen sentido (y las funciones de serialización regulares no se pueden usar).

Sin embargo, a medida que aumenta la funcionalidad perteneciente a estas clases, tiene sentido para mí encapsular sus funciones de utilidad (como el guardado() del usuario en este caso) dentro de la clase real. Sin embargo, esto significa que ya no son estrictamente objetos de valor, y ahí es donde me encuentro con mi problema antes mencionado.

una idea que tuve tendría el nombre de la clase especificada dentro de la cadena JSON, y probablemente terminar así:

$string = '{"name": "testUser", "password": "testPassword", "class": "User"}'; 
$object = json_decode ($string); 
$user = ($user->class) $object; 

Y lo contrario sería la configuración de la propiedad de clase durante/después json_encode. Lo sé, un poco intrincado, pero solo intento mantener el código relacionado. Probablemente terminaré eliminando mis funciones de utilidad de las clases nuevamente; el enfoque del constructor modificado parece un poco opaco y tiene problemas con los objetos anidados.

Agradezco esto y cualquier comentario futuro, sin embargo.

+0

Eche un vistazo al mundo J2EE donde crearía una clase separada para encapsular las operaciones que estaría realizando en el objeto de solo datos. En este caso, tal vez Usuario y UserHandler. –

+0

He actualizado mi respuesta para abordar objetos anidados. –

3

Se puede crear un factoryClass 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 quería, pero se vuelve a realizar su trabajo.

0

Para responder a su pregunta directa, no, no hay necesidad de hacerlo 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 usted está interesado en la recreación de los objetos de JSON, una posible solución es un método estático en todos los objetos de la jerarquía que acepta una stdClass/cadena y rellena las variables que se ve algo como esto

//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 transmita 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.

-1

Debo decir que estoy un poco consternado por el hecho de que no se trata solo de la funcionalidad estándar, de una biblioteca, sino de JSON. ¿Por qué no desea desea tener objetos esencialmente similares en ambos lados? (Por lo que JSON le permitirá, de todos modos)

¿Me falta algo aquí? ¿Hay una biblioteca que hace esto? (AFAICT, ninguno de [de segunda mano, tampones de protocolo, Avro] en realidad tienen API de JavaScript para que mi problema, estoy más interesado en JS < -.> PHP, algo también en JS < -.> Pitón)

+0

Los formatos de datos puros, como JSON o XML, tratan solo con la * representación * de datos, no con la interpretación o * significado * de los datos; puede usar éstos (o cualquier cantidad de otros formatos) para serializar objetos (o cualquier cantidad de otras cosas) pero la serialización en sí misma está más allá del alcance de los formatos de datos genéricos. –

9

Pregunta anterior, 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; 
    } 
} 

que lo utilice al heredar en una clase que tiene los valores que su JSON tendrá: el uso

class MyObject extends JsonDeserializer 
{ 
    /** @var string */ 
    public $property1; 

    /** @var string */ 
    public $property2; 

    /** @var string */ 
    public $property3; 

    /** @var array */ 
    public $array1; 
} 

Ejemplo:

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

Usted puede utilizar el método ::DeserializeArray si su JSON contiene una matriz de su objeto de destino.

Here es una muestra que se puede ejecutar.

+0

Excelente. Tomé parte de su método Deserialize() y lo usé en mi constructor que recibe el JSON como parámetro. De esta manera puedo crear una nueva clase, pasándola en el JSON y el resultado es una instancia de mi clase con los datos poblados en ella. Gracias. –

1

Tal vez el patrón hydration puede ser de ayuda.

Básicamente instancia un nuevo objeto vacío (new User()) y luego rellena las propiedades con los valores del objeto StdClass. Por ejemplo, podría tener un método hydrate en User.

Si es posible en su caso, puede hacer que Userconstructor acepte un parámetro opcional del tipo StdClass y tome los valores en instanciación.

Cuestiones relacionadas