Serializar el objeto PHP en JSON

101

Así que estaba deambulando por php.net en busca de información sobre la serialización de objetos PHP en JSON, cuando me encontré con la nueva interfaz JsonSerializable . Sin embargo, es solo PHP> = 5.4 y estoy ejecutando en un entorno 5.3.x.

¿Cómo se logra este tipo de funcionalidad PHP <5.4 ?

Todavía no he trabajado mucho con JSON, pero estoy tratando de admitir una capa de API en una aplicación, y descargar el objeto de datos ( que de otro modo se enviaría a la vista ) en JSON sería perfecto.

Si intento serializar el objeto directamente, devuelve una cadena JSON vacía; que se debe a que supongo json_encode()que no sabe qué diablos hacer con el objeto. ¿Debo recursivamente reducir el objeto en una matriz, y luego codificar que ?


Ejemplo

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

echo json_encode($data) produce un objeto vacío:

{}

var_dump($data) sin embargo, funciona como se esperaba:

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

Apéndice

1)

Entonces esta es la toArray()función que he ideado para la Mf_Dataclase:

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

Sin embargo, dado que los Mf_Dataobjetos también tienen una referencia a su objeto principal ( contenedor ), esto falla con la recursividad. Sin embargo, funciona como un encanto cuando elimino la _parentreferencia.

2)

Solo para continuar, la función final para transformar un objeto complejo de nodo de árbol con el que fui fue:

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

Estoy haciendo un seguimiento de nuevo, con una implementación un poco más limpia. El uso de interfaces para una instanceofverificación parece mucho más limpio que method_exists()( sin embargo method_exists(), la herencia / implementación transversal ).

El uso también unset()parecía un poco complicado, y parece que la lógica debería refactorizarse en otro método. Sin embargo, esta aplicación hace copiar la matriz de propiedad ( debido aarray_diff_key ), por lo que algo a tener en cuenta.

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

}
Dan Lugg
fuente
4
+1 Buena pregunta, todavía no conocía esta función.
toma el
@takeshin - Sí, la fecha de edición en la página del documento es de hace 4 días. ¡Me alegro de verlo!
Dan Lugg
2
Como referencia a otros que miran esto, json_encode puede manejar objetos sin problemas. Sin embargo, solo codifica miembros públicos de ese objeto. Entonces, si tiene variables de clase protegidas o privadas, entonces necesita uno de los métodos publicados o JsonSerializable.
Matthew Herbst
@MatthewHerbst Ciertamente. La vieja pregunta es vieja ahora, y <5.4 ya no es realmente una opción de todos modos (o al menos no debería serlo) DefinitivamenteJsonSerializable
Dan Lugg

Respuestas:

45

editar : actualmente es 2016-09-24, PHP 5.4 ha sido lanzado 2012-03-01, y el soporte ha finalizado 2015-09-01. Aún así, esta respuesta parece ganar votos positivos. Si todavía usa PHP <5.4, está creando un riesgo de seguridad y poniendo en peligro su proyecto . Si no tiene razones convincentes para permanecer en <5.4, o incluso si ya usa la versión> = 5.4, no use esta respuesta , y simplemente use PHP> = 5.4 (o, ya sabe, uno reciente) e implemente la interfaz JsonSerializable


Definiría una función, por ejemplo nombrada getJsonData();, que devolvería una matriz, un stdClassobjeto o algún otro objeto con parámetros visibles en lugar de privados / protegidos, y haría un json_encode($data->getJsonData());. En esencia, implemente la función de 5.4, pero llámela a mano.

Algo como esto funcionaría, como get_object_vars()se llama desde dentro de la clase, teniendo acceso a variables privadas / protegidas:

function getJsonData(){
    $var = get_object_vars($this);
    foreach ($var as &$value) {
        if (is_object($value) && method_exists($value,'getJsonData')) {
            $value = $value->getJsonData();
        }
    }
    return $var;
}
Wrikken
fuente
2
Gracias @Wrikken - ¿Existe algún atajo para reducir un objeto, los objetos contenidos en él ( todos los miembros independientemente de su visibilidad o tipo ) a una matriz asociativa, o para encasillarlo stdClass? Estoy pensando en la dirección de Reflexión , pero si no, simplemente encontraré algo para realizarlo de forma recursiva.
Dan Lugg
La reflexión sería el camino más largo. Como está dentro de la clase en su getJsonData()función, puede simplemente llamar get_object_vars()y recorrer ese resultado buscando más objetos.
Wrikken
Casi lo soluciono; el problema ahora es la recursividad. Cada objeto tiene una _parentpropiedad para que el árbol pueda atravesarse hasta la raíz. Vea mi edición para una actualización; tal vez debería hacer otra pregunta, ya que este tema ahora se abstrae de mi original.
Dan Lugg
Un simple unset($array['_parent']);antes de la caminata debería ser suficiente.
Wrikken
Genial, gracias @Wrikken. Estaba empezando a probar pruebas de igualdad complicadas, pasando un objeto de contexto $parentcomo datos de usuario a array_walk_recursive(). ¡Lo simple es hermoso! Además, es $array["\0class\0property"]debido a la contaminación de byte nulo porque estaba usando fundición. Creo que cambiaré a get_object_vars().
Dan Lugg
91

En los casos más simples, las sugerencias de tipo deberían funcionar:

$json = json_encode( (array)$object );
tomando
fuente
7
Esto da nombres de propiedad largos / feos si trabaja con espacios de nombres y cargador automático.
BetaRide
esta es la mejor solución, precisa y concisa!
Sujal Mandal
4
¿Hay alguna forma de obtener nombres de propiedad más limpios?
Christoffer
5
¿Por qué agrega \ u0000 * \ u0000 al comienzo de los nombres de las propiedades?
Elia Weiss
1
Inútil con propiedades privadas. Todos deberían aprender sobre en.wikipedia.org/wiki/Open/closed_principle .
Fabian Picone
19

json_encode()solo codificará las variables de miembros públicos. Entonces, si desea incluir lo privado una vez, debe hacerlo usted mismo (como sugirieron los demás)

jfried
fuente
8

El siguiente código está haciendo el trabajo usando la reflexión. Asume que tiene captadores para las propiedades que desea serializar

    <?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;
    }  
} 
Danny Yeshurun
fuente
1
¡Estoy tan enamorado de ti ahora mismo! Te enviaré un poco de tocino o cerveza o una magdalena, ¿y una magdalena?
Jonathan dos Santos
esta es una gran clase! también funciona con elementos de objeto protegido.
Roelof Berkepeis
2

Dado que su tipo de objeto es personalizado, tendería a estar de acuerdo con su solución: dividirlo en segmentos más pequeños utilizando un método de codificación (como JSON o serializar el contenido) y, en el otro extremo, tener el código correspondiente para reconstruir el objeto.

barfoon
fuente
2

Mi version:

json_encode(self::toArray($ob))

Implementación:

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

John Tribe
fuente
Exactamente lo que estaba buscando. Resuelve el problema con los privados. Sencillo y pequeño.
Fabian Picone
1

Intenta usar esto, esto funcionó bien para mí.

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

Cambie a sus tipos de variables privateparapublic

Esto es simple y más legible.

Por ejemplo

No funciona;

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

Está funcionando;

class A{
   public $var1="valuevar1";
   public $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}
Ferhat KOÇER
fuente
es muy extraño. pero es verdad.
Abilogos
0

Hice una buena clase auxiliar que convierte un objeto con métodos get en una matriz. No se basa en propiedades, solo métodos.

Entonces tengo el siguiente objeto de revisión que contiene dos métodos:

revisión

  • getAmountReviews: int
  • getReviews: conjunto de comentarios

Comentario

  • getSubject
  • getDescription

El script que escribí lo transformará en una matriz con propiedades que se parecen a esto:

    {
      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."
    }
]}

Fuente: Serializador de PHP que convierte un objeto en una matriz que se puede codificar en JSON.

Todo lo que tiene que hacer es envolver json_encode alrededor de la salida.

Alguna información sobre el guión:

  • Solo se agregan los métodos que comienzan con get
  • Los métodos privados se ignoran
  • Se ignora el constructor
  • Los caracteres en mayúscula en el nombre del método se reemplazarán por un carácter de subrayado y en minúsculas
Jamie
fuente
-7

Pasé algunas horas con el mismo problema. Mi objeto para convertir contiene muchas otras cuyas definiciones se supone que no debo tocar (API), así que se me ocurrió una solución que podría ser lenta, supongo, pero la estoy usando con fines de desarrollo.

Este convierte cualquier objeto en matriz.

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

Esto convierte cualquier objeto a 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;
}
SharkyDog
fuente
Hay otra respuesta fina y acertada, ya aceptada. ¿Su respuesta agrega algo radicalmente diferente, más eficiente o compacto? Supongo que no
Yaroslav
Voy a ser honesto; No creo que esto responda a la pregunta en absoluto.
Dan Lugg
5
Han pasado unos 6 meses; He regresado aquí periódicamente debido a los votos a favor y para realizar algunas modificaciones para futuros visitantes; Yo todavía no tengo idea de qué demonios se supone que hacer.
Dan Lugg
unlink($thisAnswer);
Dan Lugg
la gente tiende a rechazar lo que no entiende. Puede que no sea una solución exacta, pero es algo que hay que estudiar. En tal caso, solicita aclaraciones antes de los votos negativos.
Gimali