Sérialisation d'un objet PHP en JSON


101

Je me promenais donc dans php.net pour obtenir des informations sur la sérialisation des objets PHP en JSON, lorsque je suis tombé sur la nouvelle interface JsonSerializable . C'est seulement PHP> = 5.4 cependant, et j'exécute dans un environnement 5.3.x.

Comment ce type de fonctionnalité est-il réalisé? PHP <5.4 ?

Je n'ai pas encore beaucoup travaillé avec JSON, mais j'essaie de prendre en charge une couche API dans une application et de vider l'objet de données ( qui serait autrement envoyé à la vue ) dans JSON serait parfait.

Si j'essaye de sérialiser l'objet directement, il renvoie une chaîne JSON vide; qui est parce que je suppose json_encode()ne sait pas ce que diable faire avec l'objet. Dois-je réduire récursivement l'objet dans un tableau, puis encoder cela ?


Exemple

$data = new Mf_Data();
$data->foo->bar['hello'] = 'world';

echo json_encode($data) produit un objet vide:

{}

var_dump($data) cependant, fonctionne comme prévu:

object(Mf_Data)#1 (5) {
  ["_values":"Mf_Data":private]=>
  array(0) {
  }
  ["_children":"Mf_Data":private]=>
  array(1) {
    [0]=>
    array(1) {
      ["foo"]=>
      object(Mf_Data)#2 (5) {
        ["_values":"Mf_Data":private]=>
        array(0) {
        }
        ["_children":"Mf_Data":private]=>
        array(1) {
          [0]=>
          array(1) {
            ["bar"]=>
            object(Mf_Data)#3 (5) {
              ["_values":"Mf_Data":private]=>
              array(1) {
                [0]=>
                array(1) {
                  ["hello"]=>
                  string(5) "world"
                }
              }
              ["_children":"Mf_Data":private]=>
              array(0) {
              }
              ["_parent":"Mf_Data":private]=>
              *RECURSION*
              ["_key":"Mf_Data":private]=>
              string(3) "bar"
              ["_index":"Mf_Data":private]=>
              int(0)
            }
          }
        }
        ["_parent":"Mf_Data":private]=>
        *RECURSION*
        ["_key":"Mf_Data":private]=>
        string(3) "foo"
        ["_index":"Mf_Data":private]=>
        int(0)
      }
    }
  }
  ["_parent":"Mf_Data":private]=>
  NULL
  ["_key":"Mf_Data":private]=>
  NULL
  ["_index":"Mf_Data":private]=>
  int(0)
}

Addenda

1)

toArray()Voici donc la fonction que j'ai conçue pour la Mf_Dataclasse:

public function toArray()
{
    $array = (array) $this;
    array_walk_recursive($array, function (&$property) {
        if ($property instanceof Mf_Data) {
            $property = $property->toArray();
        }
    });
    return $array;
}

Cependant, comme les Mf_Dataobjets ont également une référence à leur objet parent ( contenant ), cela échoue avec la récursivité. Fonctionne comme un charme quand je supprime la _parentréférence.

2)

Juste pour faire un suivi, la fonction finale pour transformer un objet complexe de nœud d'arbre avec lequel je suis allé était:

// class name - Mf_Data
// exlcuded properties - $_parent, $_index
public function toArray()
{
    $array = get_object_vars($this);
    unset($array['_parent'], $array['_index']);
    array_walk_recursive($array, function (&$property) {
        if (is_object($property) && method_exists($property, 'toArray')) {
            $property = $property->toArray();
        }
    });
    return $array;
}

3)

Je fais un suivi à nouveau, avec une mise en œuvre un peu plus claire. L'utilisation d'interfaces pour une instanceofvérification semble beaucoup plus propre que method_exists()( cependant l' method_exists()héritage / l'implémentation croisée ).

L'utilisation unset()semblait aussi un peu compliquée, et il semble que la logique devrait être refactorisée dans une autre méthode. Toutefois, cette mise en œuvre ne copier le tableau de la propriété ( en raison dearray_diff_key ), donc quelque chose à considérer.

interface ToMapInterface
{

    function toMap();

    function getToMapProperties();

}

class Node implements ToMapInterface
{

    private $index;
    private $parent;
    private $values = array();

    public function toMap()
    {
        $array = $this->getToMapProperties();
        array_walk_recursive($array, function (&$value) {
            if ($value instanceof ToMapInterface) {
                $value = $value->toMap();
            }
        });
        return $array;
    }

    public function getToMapProperties()
    {
        return array_diff_key(get_object_vars($this), array_flip(array(
            'index', 'parent'
        )));
    }

}

4
+1 Bonne question, je ne connaissais pas encore cette fonctionnalité.
prend le

@takeshin - Oui, la date de modification sur la page de documentation est il y a 4 jours. Je suis content de le voir!
Dan Lugg

2
Pour référence à d'autres personnes qui regardent cela, json_encode peut très bien gérer les objets. Cependant, il n'encode que les membres publics de cet objet. Donc, si vous avez des variables de classe protégées ou privées, vous avez besoin de l'une des méthodes publiées ou de JsonSerializable.
Matthew Herbst

@MatthewHerbst Certainement. La vieille question est ancienne maintenant, et <5,4 n'est plus vraiment une option de toute façon (ou du moins ne devrait pas l'être) CertainementJsonSerializable
Dan Lugg

Réponses:


45

edit : nous sommes actuellement le 24/09/2016, et PHP 5.4 a été publié le 01/03/2012, et le support a pris fin le 01/09/2015. Pourtant, cette réponse semble gagner des votes positifs. Si vous utilisez toujours PHP <5.4, vous créez un risque de sécurité et mettez fin à votre projet . Si vous n'avez aucune raison impérieuse de rester à <5.4, ou même d'utiliser déjà la version> = 5.4, n'utilisez pas cette réponse , et utilisez simplement PHP> = 5.4 (ou, vous savez, une version récente) et implémentez l'interface JsonSerializable


Vous définiriez une fonction, par exemple nommée getJsonData();, qui renverrait soit un tableau, un stdClassobjet, soit un autre objet avec des paramètres visibles plutôt que des paramètres privés / protégés, et faire un json_encode($data->getJsonData());. En gros, implémentez la fonction de 5.4, mais appelez-la à la main.

Quelque chose comme ça fonctionnerait, comme get_object_vars()on l'appelle depuis l'intérieur de la classe, en ayant accès aux variables privées / protégées:

function getJsonData(){
    $var = get_object_vars($this);
    foreach ($var as &$value) {
        if (is_object($value) && method_exists($value,'getJsonData')) {
            $value = $value->getJsonData();
        }
    }
    return $var;
}

2
Merci @Wrikken - Existe-t-il un raccourci pour réduire un objet, les objets qu'il contient ( tous les membres indépendamment de la visibilité ou du type ) en un tableau associatif, ou le transtyper en stdClass? Je pense dans la direction de Reflection , mais sinon, je vais juste trouver quelque chose pour l'exécuter de manière récursive.
Dan Lugg

La réflexion serait le long chemin. Lorsque vous êtes à l' intérieur de la classe de votre getJsonData()fonction, vous pouvez simplement appeler get_object_vars()et parcourir ce résultat en recherchant plus d'objets.
Wrikken

J'ai presque tout réglé; le problème est maintenant la récursivité. Chaque objet a une _parentpropriété pour que l'arborescence puisse être parcourue jusqu'à la racine. Voir ma modification pour une mise à jour; peut-être devrais-je poser une autre question car cette question est maintenant extraite de mon original.
Dan Lugg

Un simple unset($array['_parent']);avant la marche devrait faire l'affaire.
Wrikken

Génial, merci @Wrikken - Je commençais à essayer des tests d'égalité compliqués, en passant un objet de contexte en $parenttant que données utilisateur à array_walk_recursive(). Le simple est beau! De plus, c'est à $array["\0class\0property"]cause de la pollution de zéro octet parce que j'utilisais la diffusion. Je pense que je vais passer à get_object_vars().
Dan Lugg

91

Dans les cas les plus simples, l'indication de type devrait fonctionner:

$json = json_encode( (array)$object );

7
Cela donne des noms de propriété longs / laids si vous travaillez avec des espaces de noms et un chargeur automatique.
BetaRide

c'est la meilleure solution, précise et concise!
Sujal Mandal

4
existe-t-il un moyen d'obtenir des noms de propriétés plus propres?
Christoffer

5
pourquoi ajoute-t-il \ u0000 * \ u0000 au début des noms d'accessoires?
Elia Weiss

1
Inutile avec les propriétés privées. Vous devriez tous en savoir plus sur en.wikipedia.org/wiki/Open/closed_principle .
Fabian Picone

19

json_encode()ne codera que les variables membres publiques. donc si vous souhaitez inclure le privé une fois que vous devez le faire vous-même (comme les autres l'ont suggéré)


8

Le code suivant fait le travail en utilisant la réflexion. Il suppose que vous avez des getters pour les propriétés que vous souhaitez sérialiser

    <?php

    /**
     * Serialize a simple PHP object into json
     * Should be used for POPO that has getter methods for the relevant properties to serialize
     * A property can be simple or by itself another POPO object
     *
     * Class CleanJsonSerializer
     */
    class CleanJsonSerializer {

    /**
     * Local cache of a property getters per class - optimize reflection code if the same object appears several times
     * @var array
     */
    private $classPropertyGetters = array();

    /**
     * @param mixed $object
     * @return string|false
     */
    public function serialize($object)
    {
        return json_encode($this->serializeInternal($object));
    }

    /**
     * @param $object
     * @return array
     */
    private function serializeInternal($object)
    {
        if (is_array($object)) {
            $result = $this->serializeArray($object);
        } elseif (is_object($object)) {
            $result = $this->serializeObject($object);
        } else {
            $result = $object;
        }
        return $result;
    }

    /**
     * @param $object
     * @return \ReflectionClass
     */
    private function getClassPropertyGetters($object)
    {
        $className = get_class($object);
        if (!isset($this->classPropertyGetters[$className])) {
            $reflector = new \ReflectionClass($className);
            $properties = $reflector->getProperties();
            $getters = array();
            foreach ($properties as $property)
            {
                $name = $property->getName();
                $getter = "get" . ucfirst($name);
                try {
                    $reflector->getMethod($getter);
                    $getters[$name] = $getter;
                } catch (\Exception $e) {
                    // if no getter for a specific property - ignore it
                }
            }
            $this->classPropertyGetters[$className] = $getters;
        }
        return $this->classPropertyGetters[$className];
    }

    /**
     * @param $object
     * @return array
     */
    private function serializeObject($object) {
        $properties = $this->getClassPropertyGetters($object);
        $data = array();
        foreach ($properties as $name => $property)
        {
            $data[$name] = $this->serializeInternal($object->$property());
        }
        return $data;
    }

    /**
     * @param $array
     * @return array
     */
    private function serializeArray($array)
    {
        $result = array();
        foreach ($array as $key => $value) {
            $result[$key] = $this->serializeInternal($value);
        }
        return $result;
    }  
} 

1
Je suis tellement amoureux de toi en ce moment! Je t'enverrai du bacon ou de la bière ou un cupcake et un cupcake?
Jonathan dos Santos

c'est une excellente classe! il fonctionne également avec les éléments d'objets protégés.
Roelof Berkepeis


2

Étant donné que votre type d'objet est personnalisé, j'aurais tendance à être d'accord avec votre solution - décomposez-la en segments plus petits à l'aide d'une méthode de codage (comme JSON ou sérialisation du contenu), et à l'autre extrémité, j'ai le code correspondant pour reconstruire l'objet.


2

Ma version:

json_encode(self::toArray($ob))

La mise en oeuvre:

private static function toArray($object) {
    $reflectionClass = new \ReflectionClass($object);

    $properties = $reflectionClass->getProperties();

    $array = [];
    foreach ($properties as $property) {
        $property->setAccessible(true);
        $value = $property->getValue($object);
        if (is_object($value)) {
            $array[$property->getName()] = self::toArray($value);
        } else {
            $array[$property->getName()] = $value;
        }
    }
    return $array;
}

JsonUtils: GitHub


Exactement ce que je cherchais. Résout le problème avec les privés. Simple et petit.
Fabian Picone

1

Essayez d'utiliser ceci, cela a bien fonctionné pour moi.

json_encode(unserialize(serialize($array)));

1

Remplacez vos types de variables privateparpublic

C'est simple et plus lisible.

Par exemple

Ca ne fonctionne pas;

class A{
   private $var1="valuevar1";
   private $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}

Ça fonctionne;

class A{
   public $var1="valuevar1";
   public $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}

c'est très étrange. mais c'est vrai.
Abilogos

0

J'ai créé une belle classe d'assistance qui convertit un objet avec des méthodes get en un tableau. Il ne repose pas sur des propriétés, juste des méthodes.

J'ai donc l'objet de révision suivant qui contient deux méthodes:

La revue

  • getAmountReviews: int
  • getReviews: tableau de commentaires

Commentaire

  • getSubject
  • getDescription

Le script que j'ai écrit le transformera en un tableau avec des propriétés qui ressemblent à ceci:

    {
      amount_reviews: 21,
      reviews: [
        {
          subject: "In een woord top 1!",
          description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor."
        },
        {
          subject: "En een zwembad 2!",
          description: "Maecenas et aliquet mi, a interdum mauris. Donec in egestas sem. Sed feugiat commodo maximus. Pellentesque porta consectetur commodo. Duis at finibus urna."
        },
        {
          subject: "In een woord top 3!",
          description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor."
        },
        {
          subject: "En een zwembad 4!",
          description: "Maecenas et aliquet mi, a interdum mauris. Donec in egestas sem. Sed feugiat commodo maximus. Pellentesque porta consectetur commodo. Duis at finibus urna."
       },
       {
          subject: "In een woord top 5!",
          description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque laoreet lacus quis eros venenatis, sed tincidunt mi rhoncus. Aliquam ut pharetra diam, nec lobortis dolor."
    }
]}

Source: PHP Serializer qui convertit un objet en un tableau pouvant être encodé en JSON.

Tout ce que vous avez à faire est d'enrouler json_encode autour de la sortie.

Quelques informations sur le script:

  • Seules les méthodes commençant par get sont ajoutées
  • Les méthodes privées sont ignorées
  • Le constructeur est ignoré
  • Les caractères majuscules dans le nom de la méthode seront remplacés par un trait de soulignement et un caractère minuscule

-7

J'ai passé quelques heures sur le même problème. Mon objet à convertir en contient beaucoup d'autres dont je ne suis pas censé toucher aux définitions (API), j'ai donc trouvé une solution qui pourrait être lente je suppose, mais je l'utilise à des fins de développement.

Celui-ci convertit n'importe quel objet en tableau

function objToArr($o) {
$s = '<?php
class base {
    public static function __set_state($array) {
        return $array;
    }
}
function __autoload($class) {
    eval("class $class extends base {}");
}
$a = '.var_export($o,true).';
var_export($a);
';
$f = './tmp_'.uniqid().'.php';
file_put_contents($f,$s);
chmod($f,0755);
$r = eval('return '.shell_exec('php -f '.$f).';');
unlink($f);
return $r;
}

Cela convertit n'importe quel objet en stdClass

class base {
    public static function __set_state($array) {
        return (object)$array;
    }
}
function objToStd($o) {
$s = '<?php
class base {
    public static function __set_state($array) {
        $o = new self;
        foreach($array as $k => $v) $o->$k = $v;
        return $o;
    }
}
function __autoload($class) {
    eval("class $class extends base {}");
}
$a = '.var_export($o,true).';
var_export($a);
';
$f = './tmp_'.uniqid().'.php';
file_put_contents($f,$s);
chmod($f,0755);
$r = eval('return '.shell_exec('php -f '.$f).';');
unlink($f);
return $r;
}

Il y a une autre réponse fine et précise, déjà acceptée. Votre réponse ajoute-t-elle quelque chose de radicalement différent, de plus efficace ou de plus compact? Je suppose que non
Yaroslav

Je vais être honnête; Je ne pense pas du tout que cela réponde à la question.
Dan Lugg

5
Cela fait environ 6 mois; Je suis revenu périodiquement ici en raison de votes positifs et pour apporter quelques modifications aux futurs visiteurs; Je n'ai toujours aucune idée de ce que c'est censé faire.
Dan Lugg

unlink($thisAnswer);
Dan Lugg

1
inline php strings, eval, shell_exec(php)... cc-combo.
vp_arth
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.