2009-03-18 14 views
17

Supponiamo di avere una classe utente con le proprietà 'name' e 'password' e un metodo 'save'. Quando serializzi un oggetto di questa classe su JSON tramite json_encode, il metodo viene saltato correttamente e finisco con qualcosa come {'name': 'testName', 'password': 'testPassword'}.Deserializzazione da JSON in PHP, con casting?

Tuttavia, durante la deserializzazione tramite json_decode, si ottiene un oggetto StdClass anziché un oggetto Utente, il che ha senso, ma ciò significa che l'oggetto non ha il metodo "salva". C'è un modo per lanciare l'oggetto risultante come utente, o fornire qualche suggerimento a json_decode sul tipo di oggetto che mi aspetto?

risposta

9

Penso che il modo migliore per gestire questo sarebbe tramite il costruttore, direttamente o tramite una fabbrica:

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

Esattamente. JSON non ha modo di codificare il tipo di oggetto originale. – Powerlord

9

Risposta breve: No (non che io sappia *)

Risposta lunga : json_encode serializzerà solo variabili pubbliche. Come si può vedere dal JSON spec, non esiste un tipo di dati "funzione". Questi sono entrambi i motivi per cui i tuoi metodi non sono serializzati nel tuo oggetto JSON.

Ryan Graham ha ragione: l'unico modo per ricreare questi oggetti come istanze non-stdClass è ricrearli dopo la deserializzazione.

Esempio

<?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(); 

In alternativa, a meno che non si ha realmente bisogno i dati come JSON, si può semplicemente utilizzare normal serialization e sfruttare il __sleep() and __wakeup() hooks per ottenere un'ulteriore personalizzazione.

* In a previous question of my own è stato suggerito di poter implementare alcune delle interfacce SPL per personalizzare l'input/output di json_encode() ma i miei test hanno rivelato che si trattava di inseguimenti oculari selvaggi.

1

Sono consapevole del fatto che JSON non supporta la serializzazione delle funzioni, che è perfettamente accettabile e persino desiderata. Le mie classi sono attualmente utilizzate come oggetti valore nella comunicazione con JavaScript e le funzioni non hanno alcun significato (e le normali funzioni di serializzazione non sono utilizzabili).

Tuttavia, poiché la funzionalità di queste classi aumenta, incapsulare le loro funzioni di utilità (come ad esempio un save dell'utente() in questo caso) all'interno della classe effettiva ha senso per me. Ciò significa che non sono più strettamente oggetti di valore, ed è lì che mi imbatto nel mio problema di cui sopra.

un'idea che ho avuto avrebbe il nome della classe specificato all'interno della stringa JSON, e probabilmente finire così:

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

E il contrario sarebbe impostando la proprietà di classe durante/dopo json_encode. Lo so, un po 'contorto, ma sto solo cercando di mantenere insieme il codice correlato. Probabilmente finirò per riprendere le mie funzioni di utilità dalle classi; l'approccio del costruttore modificato sembra un po 'opaco e si mette nei guai con oggetti nidificati.

Apprezzo questo e qualsiasi feedback futuro, tuttavia.

+0

Dai un'occhiata al mondo J2EE in cui crei una classe separata per incapsulare le operazioni che verrebbero eseguite sull'oggetto solo dati. In questo caso, forse User e UserHandler. –

+0

Ho aggiornato la mia risposta per indirizzare gli oggetti nidificati. –

3

si potrebbe creare un factoryClass di qualche tipo:

function create(array $data) 
{ 
    $user = new User(); 
    foreach($data as $k => $v) { 
     $user->$k = $v; 
    } 
    return $user; 
} 

Non è come la soluzione si voleva, ma si ottiene il vostro lavoro fatto.

0

Per rispondere alla domanda diretta, no, non è stato possibile farlo con json_encode/json_decode. JSON è stato progettato e specificato per essere un formato per la codifica delle informazioni e non per la serializzazione di oggetti. La funzione PHP non va oltre.

Se siete interessati a ricreare gli oggetti da JSON, una possibile soluzione è un metodo statico su tutti gli oggetti nella gerarchia che accetta una stdClass/string e popola le variabili che sembra qualcosa di simile

//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; 
} 

Non l'ho provato, ma spero che mi dia l'impressione. Idealmente, tutti gli oggetti dovrebbero estendersi da qualche oggetto di base (di solito chiamato "oggetto di classe") in modo da poter aggiungere questo codice solo in un posto.

-1

Devo dire che sono un po 'costernato che questa non è solo la funzionalità standard, off the shelf - di qualche libreria, se non la stessa JSON. Perché lo non sarebbe che si desidera avere essenzialmente oggetti simili su entrambi i lati? (Per quanto riguarda JSON, comunque)

Mi manca qualcosa qui? C'è una biblioteca che fa questo? (AFAICT, nessuno dei [parsimoni, protocolli buffer, avro] in realtà ha API per javascript.Per il mio problema, sono più interessato a JS < -> PHP, un po 'anche in JS < -> python.)

+0

I formati di dati puri, come JSON o XML, riguardano solo la * rappresentazione * dei dati, non con l'interpretazione o * il significato * dei dati - è possibile utilizzare questi (o qualsiasi numero di altri formati) per serializzare oggetti (o qualsiasi numero di altre cose) ma la serializzazione stessa va oltre lo scopo dei formati di dati generici. –

9

Vecchia domanda, ma forse qualcuno lo troverà utile.
Ho creato una classe astratta con funzioni statiche che è possibile ereditare sul proprio oggetto per deserializzare qualsiasi JSON nell'istanza della classe ereditaria.

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 si utilizza ereditando su una classe che ha i valori che il vostro JSON avrà: l'utilizzo

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

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

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

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

Esempio:

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

È possibile utilizzare il metodo ::DeserializeArray se il tuo JSON contiene una matrice del tuo oggetto target.

Here è un esempio eseguibile.

+0

Eccellente. Ho preso parte al tuo metodo Deserialize() e l'ho usato nel mio costruttore che riceve il JSON come parametro. In questo modo posso creare una nuova classe, passarla nel JSON e il risultato è un'istanza della mia classe con i dati popolati in essa. Grazie. –

1

Forse il modello hydration può essere di aiuto.

Fondamentalmente si crea un nuovo oggetto vuoto (new User()) e quindi si inseriscono le proprietà con i valori dell'oggetto StdClass. Ad esempio, potresti avere un metodo hydrate in User.

Se possibile nel tuo caso, è possibile rendere il Userconstructor accettare un parametro opzionale di tipo StdClass e prendere i valori a esemplificazione.

Problemi correlati