2009-03-18 22 views
17

Angenommen, ich habe eine Benutzerklasse mit den Eigenschaften 'name' und 'password' und eine 'save' Methode. Wenn ein Objekt dieser Klasse über Json_encode in JSON serialisiert wird, wird die Methode ordnungsgemäß übersprungen und ich bekomme etwas wie {'name': 'testName', 'password': 'testPassword'}.Deserialisieren von JSON in PHP mit Casting?

Wenn ich jedoch über json_decode deserialisiere, habe ich am Ende ein StdClass-Objekt anstelle eines User-Objekts, was sinnvoll ist, aber das bedeutet, dass dem Objekt die 'save'-Methode fehlt. Gibt es eine Möglichkeit, das resultierende Objekt als Benutzer zu verwenden oder einen Hinweis an json_decode zu geben, welcher Objekttyp ich erwarte?

Antwort

9

Ich denke, der beste Weg, dies über den Konstruktor zu handhaben wäre, entweder direkt oder über eine Fabrik:

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

. JSON hat keine Möglichkeit zu codieren, welche Art von Objekt das Original war. – Powerlord

9

Kurze Antwort: Nein (nicht dass ich wüsste *)

Lange Antwort : json_encode wird nur öffentliche Variablen serialisieren. Wie Sie an der JSON spec sehen können, gibt es keinen "Funktion" -Datentyp. Aus diesen Gründen werden Ihre Methoden nicht in Ihr JSON-Objekt serialisiert.

Ryan Graham hat recht - die einzige Möglichkeit, diese Objekte als Nicht-stdClass-Instanzen neu zu erstellen, besteht darin, sie nach der Deserialisierung neu zu erstellen.

Beispiel

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

Alternativ, wenn Sie wirklich die Daten als JSON, dann können Sie nur normal serialization verwenden und die __sleep() and __wakeup() hooks nutzen zusätzliche Anpassungen zu erreichen.

* In a previous question of my own wurde vorgeschlagen, dass Sie einige der SPL-Schnittstellen implementieren können, um die Eingabe/Ausgabe von json_encode() anzupassen, aber meine Tests enthüllten, dass es sich um wilde Chase handelt.

1

Ich bin mir bewusst, dass JSON die Serialisierung von Funktionen nicht unterstützt, was vollkommen akzeptabel und sogar erwünscht ist. Meine Klassen werden derzeit als Wertobjekte für die Kommunikation mit JavaScript verwendet, und Funktionen hätten keine Bedeutung (und die regulären Serialisierungsfunktionen sind nicht verwendbar).

Da die Funktionalität in Bezug auf diese Klassen jedoch zunimmt, macht es Sinn, ihre Utility-Funktionen (wie in diesem Fall die save() eines Benutzers) innerhalb der eigentlichen Klasse zu kapseln. Dies bedeutet jedoch, dass es sich nicht mehr um reine Wertobjekte handelt, und hier stoße ich auf mein oben genanntes Problem.

Eine Idee hatte ich würde die Klassennamen in dem JSON-String angegeben hat, und würde wahrscheinlich am Ende wie folgt:

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

Und umgekehrt würde die Klasseneigenschaft während/nach dem json_encode wird Einstellung. Ich weiß, ein wenig verschachtelt, aber ich versuche nur, verwandten Code zusammen zu halten. Ich werde wahrscheinlich meine Utility-Funktionen wieder aus den Klassen nehmen; Der Ansatz des modifizierten Konstruktors erscheint ein wenig undurchsichtig und führt zu Problemen mit verschachtelten Objekten.

Ich weiß dies und jede zukünftige Rückmeldung zu schätzen.

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

Es ist nicht wie die Lösung, die Sie wollten, aber es wird Ihre Arbeit geleistet:

+0

Werfen Sie einen Blick auf die J2EE-Welt, wo Sie eine eigene Klasse für die Verkapselung der Operationen schaffen würden Sie auf dem Daten nur Objekt durchführen würde. In diesem Fall vielleicht User und UserHandler. –

+0

Ich habe meine Antwort aktualisiert, um verschachtelte Objekte zu adressieren. –

3

Sie könnten eine FactoryClass irgendeiner Art erstellen.

0

Um Ihre direkte Frage zu beantworten, nein, es gibt keine, dies mit json_encode/json_decode zu tun. JSON wurde als Format zum Codieren von Informationen und nicht zum Serialisieren von Objekten entworfen und festgelegt. Die PHP-Funktion geht nicht darüber hinaus.

Wenn Sie neu zu erstellen Objekte aus JSON interessiert sind, eine mögliche Lösung ist eine statische Methode auf alle Objekte in der Hierarchie, die eine stdClass/string und auffüllt Variablen akzeptiert, die

so etwas wie dieses aussieht
//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; 
} 

Ich habe das nicht getestet, aber ich hoffe, es kommt auf die Idee. Idealerweise sollten alle Ihre Objekte von einem Basisobjekt (normalerweise "Klassenobjekt") ausgehen, damit Sie diesen Code nur an einer Stelle hinzufügen können.

-1

Ich muss sagen, ich bin ein wenig bestürzt, dass dies nicht nur Standard, von der Stange Funktionalität ist - von einigen Bibliothek, wenn nicht JSON selbst. Warum wäre nicht Sie möchten im Wesentlichen ähnliche Objekte auf beiden Seiten haben? (So ​​weit wie JSON wird Sie sowieso lassen)

Fehle ich hier etwas? Gibt es eine Bibliothek, die das tut? (AFAICT, keine von [Sparsamkeit, Protokollpuffer, Avro] haben eigentlich APIs für Javascript. Für mein Problem interessiert mich JS am meisten < -> PHP, etwas auch in JS

+0

Reine Datenformate, wie JSON oder XML, beschäftigen nur mit * Darstellung * von Daten, nicht mit der Auslegung oder * Bedeutung * der Daten - Sie können diese (oder eine beliebige Anzahl von anderen Formaten) verwenden, um Objekte serialisiert werden (oder beliebig viele andere Dinge), aber die Serialisierung selbst geht über den Rahmen generischer Datenformate hinaus. –

9

Alte Frage, aber vielleicht wird jemand das nützlich finden.
Ich habe eine abstrakte Klasse mit statischen Funktionen erstellt, die Sie in Ihrem Objekt erben können, um JSON in die erbende Klasseninstanz zu deserialisieren.

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

Sie verwenden es, indem es auf eine Klasse zu erben, die die Werte, die Ihre JSON haben:

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

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

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

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

Beispiel Nutzung:

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

Sie die ::DeserializeArray Methode verwenden können, wenn Ihr JSON enthält ein Array Ihres Zielobjekts.

Here ist ein lauffähiges Beispiel.

+0

Ausgezeichnet. Ich nahm einen Teil Ihrer Deserialize() -Methode und benutzte sie in meinem Konstruktor, der den JSON als Parameter erhält. Auf diese Weise kann ich neue Klasse erstellen, übergeben Sie in der JSON und das Ergebnis ist eine Instanz meiner Klasse mit den darin bevölkerten Daten. Vielen Dank. –

1

Vielleicht ist das hydration Muster von Hilfe sein kann.

Grundsätzlich instanziieren Sie ein neues leeres Objekt (new User()) und dann füllen Sie die Eigenschaften mit Werten aus dem Objekt StdClass. Zum Beispiel könnten Sie eine hydrate Methode in User haben.

Falls in Ihrem Fall möglich, können Sie den Userconstructor einen optionalen Parameter vom Typ StdClass akzeptieren und die Werte bei Instanziierung übernehmen. Genau

Verwandte Themen