web-dev-qa-db-fra.com

Sérialisation PHP objet vers JSON

Donc, je me promenais php.net pour plus d’informations sur la sérialisation PHP objets en JSON, quand j’ai trébuché sur le nouveau JsonSerializable Interface . C’est seulement PHP> = 5.4 cependant, et je tourne dans un environnement 5.3.x.

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

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

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


Exemple

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

echo json_encode($data) produit un objet vide:

{}

var_dump($data) fonctionne cependant 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)

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

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

Cependant, étant donné que les objets Mf_Data Ont également une référence à leur objet parent ( contenant), cela échoue avec la récursivité. Fonctionne comme un charme quand je supprime la référence _parent.

2)

Juste pour faire suite, la dernière fonction pour transformer un objet complexe de nœud d’arbre auquel 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 poursuis encore, avec un peu plus propre d'une implémentation. L'utilisation d'interfaces pour un contrôle instanceof semble beaucoup plus propre que method_exists() ( cependant method_exists() ne coupe pas l'héritage/la mise en œuvre).

Utiliser unset() semblait un peu compliqué aussi, et il semble que la logique devrait être refactorisée dans une autre méthode. Cependant, cette implémentation fait copier le tableau de propriétés ( en raison de array_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'
        )));
    }

}
98
Dan Lugg

edit : il s’agit du 2016-09-24 et PHP 5.4 a été publié le 01/03/2012, et le support a terminé 2015-09-01. Pourtant, cette réponse semble gagner des votes positifs. Si vous utilisez toujours PHP <5.4, vous créez un risque pour la sécurité et endager 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, un récent) et implémenter l'interface JsonSerializable


Vous définiriez une fonction, nommée par exemple getJsonData();, qui renverrait soit un tableau, un objet stdClass, soit un autre objet avec des paramètres visibles plutôt que des paramètres privés/protégés, et effectuerait un json_encode($data->getJsonData());. En gros, 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 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;
}
43
Wrikken

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

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

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

19
jfried

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;
    }  
} 
8
Danny Yeshurun

Il suffit de mettre en œuvre une interface donnée par PHP JsonSerializable .

5
webcodecs

É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é, utilisez le code correspondant pour reconstruire l'objet.

2
barfoon

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

1
John Tribe

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

json_encode(unserialize(serialize($array)));
1
Navaneeth Mohan

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

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

Consulter

  • getAmountReviews: int
  • getReviews: tableau de commentaires

Commentaire

  • getSubject
  • getDescription

Le script que j'ai écrit va le transformer en un tableau avec des propriétés ressemblant à 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: Serializer PHP qui convertit un objet en un tableau pouvant être encodé en JSON.

Tout ce que vous avez à faire est d’envelopper 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 en majuscule dans le nom de la méthode seront remplacés par un caractère de soulignement et un caractère en minuscule
0
Jamie