105 votes

Sérialisation d'un objet PHP en JSON

Donc, je me promenais autour de php.net pour plus d'informations sur la sérialisation des objets PHP, JSON, quand j'ai trébuché à travers le nouveau JsonSerializable Interface. C'est seulement PHP >= 5.4 si, et je suis en cours d'exécution dans un 5.3.x environnement.

Comment est-ce que ce genre de fonctionnalité atteint PHP < 5.4?

Je n'ai pas beaucoup travaillé avec JSON, mais je suis en train de soutenir une couche API dans une application, et l'immersion de l'objet de données (qui autrement seraient envoyés à l'affichage) en JSON, ce serait parfait.

Si je tente de sérialiser l'objet directement, il renvoie un vide chaîne JSON; c'est parce que je suppose json_encode() ne sait pas ce que le diable à faire avec l'objet. Dois-je réduire de manière récursive l'objet dans un tableau, et puis l'encoder qui?


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

Addendum

1)

C'est donc l' toArray() fonction que j'ai conçu pour l' Mf_Data classe:

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

Toutefois, depuis l' Mf_Data objets ont également une référence à leur parent (contenant) de l'objet, il échoue avec la récursivité. Fonctionne comme un charme, mais quand j'ai enlever l' _parent de référence.

2)

Juste pour le suivi, la dernière fonction de transformer un complexe d'arbres nœud d'objet, je suis allé avec a:

// 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, $key){
        if(is_object($property)
        && method_exists($property, 'toArray')){
            $property = $property->toArray();
        }
    });
    return $array;
}

3)

Je suis encore une fois, avec un peu de nettoyant de la mise en œuvre. L'utilisation d'interfaces pour un instanceof vérifiez semble beaucoup plus propre qu' method_exists() (toutefois method_exists() ne transversale sur les droits de succession/mise en œuvre).

À l'aide de unset() semble un peu brouillon aussi, et il semble que la logique devrait être remaniée en une autre méthode. Toutefois, cette mise en œuvre ne copie la propriété array (due à l' array_diff_key), donc quelque chose à considérer.

(Aussi, oui, ce n'utiliser PHP 5.4 syntaxe pour des raisons de concision)

interface ToArrayInterface {
    function toArray();
    function getToArrayProperties();
}


class Node implements ToArrayInterface {

    private $index;
    private $parent;
    private $values = [];

    public function toArray() {
        $array = $this->getToArrayProperties();
        array_walk_recursive($array, function(&$value, $key) {
            if ($value instanceof ToArrayInterface) {
                $value = $value->toArray();
            }
        });
        return $array;
    }

    public function getToArrayProperties() {
        return array_diff_key(get_object_vars($this), array_flip([
            'index', 'parent'
        ]));
    }

}

95voto

takeshin Points 16579

Pour les cas les plus simples, les indications de type devraient fonctionner:

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

47voto

Wrikken Points 37727

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

Quelque chose comme cela fonctionnerait, puisque get_object_vars() est appelé depuis l'intérieur de la classe, en ayant accès à des 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;
     }
 

21voto

jfried Points 398

json_encode() n'encodera que des variables de membre public. donc si vous voulez inclure le privé une fois que vous devez le faire vous-même (comme les autres l'ont suggéré)

12voto

Danny Yeshurun Points 1

Le code suivant fait le travail en utilisant la réflexion. Cela suppose que vous ayez 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;
}
 

}

2voto

barfoon Points 6999

Étant donné que votre type d'objet est personnalisé, je suis plutôt d'accord avec votre solution - divisez-la en segments plus petits à l'aide d'une méthode d'encodage (comme JSON ou sérialisation du contenu), et à l'autre extrémité, vous disposez du code correspondant pour reconstruire l'objet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X