La desérialisation de JSON en PHP, avec casting?

supposons que j'ai une classe utilisateur avec les propriétés 'name' et 'password', et une méthode 'save'. Lors de la sérialisation D'un objet de cette classe à JSON via json_encode, la méthode est correctement sautée et je finis avec quelque chose comme {'name': 'testName', 'password': 'testPassword'}.

cependant, lors de la desérialisation via json_decode, je finis avec un objet StdClass au lieu d'un objet User, ce qui a du sens, mais cela signifie que l'objet n'a pas la méthode 'save'. Est-il possible de lancer le objet résultant en tant qu'utilisateur, ou pour fournir une indication à json_decode quant au type d'objet que j'attends?

18
demandé sur Jeroen van Delft 2009-03-18 17:25:28

10 réponses

vieille question, mais peut-être que quelqu'un trouvera cela utile.

J'ai créé une classe abstraite avec des fonctions statiques que vous pouvez hériter sur votre objet afin de desérialiser n'importe quel JSON dans l'instance de classe héritant.

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

Vous l'utilisez en héritant sur une classe qui a les valeurs que votre JSON:

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

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

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

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

Exemple d'utilisation:

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

Vous pouvez utiliser le ::DeserializeArray méthode si votre JSON contient un tableau de votre cible objet.

Iciest un échantillon runnable.

10
répondu René Sackers 2015-09-28 14:02:49

réponse Courte: Non (pas que je sache*)

réponse longue: json_encode ne sérialisera que les variables publiques. Comme vous pouvez le voir par l' JSON spec, il n'y a pas de fonction "" type de données. Ce sont deux raisons pour lesquelles vos méthodes ne sont pas sérialisées dans votre objet JSON.

Ryan Graham a raison - la seule façon de recréer ces objets en tant qu'instances non-stdClass est de les recréer après la désérialisation.

Exemple

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

alternativement, sauf si vous avez vraiment besoin des données comme JSON, vous pouvez juste utiliser normal de sérialisation et tirer parti de la __sleep() et __wakeup() les crochets pour obtenir une personnalisation supplémentaire.

*une précédente question de mes propres il a été suggéré que vous pourriez implémenter certaines des interfaces SPL pour personnaliser les entrées / sorties de json_encode () mais mes tests révélé celles de l'oie sauvage chasse.

9
répondu Peter Bailey 2017-05-23 11:33:27

je pense que la meilleure façon de gérer cela pourrait être par le constructeur, soit directement, soit par l'intermédiaire d'une usine:

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)); 
   }
}
9
répondu Ryan Graham 2009-03-18 15:40:35

Vous pouvez créer un FactoryClass d'une sorte:

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

ce n'est pas comme la solution que vous vouliez, mais ça fait votre travail.

3
répondu bouke 2009-03-18 16:47:48

jetez un oeil à cette classe, j'ai écrit:

https://github.com/mindplay-dk/jsonfreeze/blob/master/mindplay/jsonfreeze/JsonSerializer.php

Il se réserve un objet JSON-propriété appelée '#type' pour stocker le nom de classe, et qu'il a certaines limites qui sont décrits ici:

Serialize/unserialize PHP objet graphique en JSON

2
répondu mindplay.dk 2017-05-23 11:47:36

je suis conscient que JSON ne supporte pas la sérialisation des fonctions, ce qui est parfaitement acceptable, et même souhaité. Mes classes sont actuellement utilisées comme objets de valeur dans la communication avec JavaScript, et les fonctions n'auraient aucune signification (et les fonctions de sérialisation régulières ne sont pas utilisables).

cependant, au fur et à mesure que la fonctionnalité relative à ces classes augmente, encapsuler leurs fonctions utilitaires (comme enregistrer() de L'utilisateur dans ce cas) à l'intérieur de la classe réelle a du sens. pour moi. Cela signifie qu'ils ne sont plus strictement des objets de valeur cependant, et c'est là que je cours dans mon problème susmentionné.

Une idée que j'ai eu aurait le nom de la classe spécifiée à l'intérieur de la chaîne JSON, et serait probablement jusqu'à la fin comme ceci:

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

et l'inverse serait de définir la propriété class during / after json_encode. Je sais, un peu alambiqué, mais j'essaye juste de garder le code relié ensemble. Je vais probablement finir par prendre mes fonctions utilitaires de la les classes à nouveau; l'approche du constructeur modifié semble un peu opaque et rencontre des problèmes avec les objets imbriqués.

je ne l'apprécie cela et tous les futurs commentaires, cependant.

1
répondu Jeroen van Delft 2009-03-18 15:29:54

un peu en retard mais une autre option est d'utiliser le serialiseur symfony pour désérialiser xml, json, n'importe quoi pour objecter.

voici la documentation: http://symfony.com/doc/current/components/serializer.html#deserializing-in-an-existing-object

1
répondu zajca 2016-08-16 08:20:23

peut-être le hydration le motif peut être utile.

en gros, vous instanciez un nouvel objet vide (new User()) et ensuite vous remplissez les propriétés avec les valeurs de StdClass objet. Par exemple, vous pourriez avoir un hydrate méthode User.

Si possible dans votre cas, vous pouvez faire l' Userconstructor accepter un paramètre optionnel de type StdClass et de prendre les valeurs à l'instanciation.

1
répondu Mihai Răducanu 2016-10-04 13:55:49

pour répondre à votre question directe, Non, il n'y avait pas à faire ça avec json_encode/json_decode. JSON a été conçu et spécifié pour être un format d'encodage de l'information, et non pour sérialiser des objets. La fonction PHP ne va pas au-delà de cela.

si vous êtes intéressé à recréer des objets à partir de JSON, une solution possible est une méthode statique sur tous les objets de votre hiérarchie qui accepte une stdClass / string et popule des variables qui ressemblent à quelque chose comme ce

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

Je n'ai pas testé cela, mais j'espère que ça fera passer l'idée. Idéalement, tous vos objets devraient s'étendre à partir d'un objet de base (généralement appelé "objet de classe") de sorte que vous pouvez ajouter ce code en un seul endroit.

0
répondu Alan Storm 2009-03-18 17:09:32

je dois dire que je suis un peu consterné que ce ne soit pas seulement une fonctionnalité standard, disponible sur le marché -- d'une bibliothèque, si ce n'est JSON lui-même. Pourquoi ne pas vous voulez avoir des objets essentiellement similaires des deux côtés? (Dans la mesure du JSON va vous laisser, de toute façon)

est-ce que je rate quelque chose ici? Est-il une bibliothèque qui fait cela? (AFAICT, aucun de [thrift, Protocol buffers, avro] n'a d'API pour javascript. Pour mon problème, je suis plus intéressée par la JS <-> PHP, un peu aussi en js <-> python .)

-1
répondu Nick Papadakis 2010-05-11 22:04:23