Serializando objeto PHP para JSON

101

Eu estava vagando pelo php.net em busca de informações sobre como serializar objetos PHP para JSON, quando me deparei com a nova interface JsonSerializable . É apenas PHP> = 5.4 , porém, e estou executando em um ambiente 5.3.x.

Como esse tipo de funcionalidade é alcançado em PHP <5.4 ?

Ainda não trabalhei muito com JSON, mas estou tentando oferecer suporte a uma camada de API em um aplicativo, e despejar o objeto de dados ( que de outra forma seria enviado para a visualização ) em JSON seria perfeito.

Se eu tentar serializar o objeto diretamente, ele retorna uma string JSON vazia; o que é porque eu presumo json_encode()que não saiba o que diabos fazer com o objeto. Devo reduzir recursivamente o objeto em um array e, em seguida, codificá- lo ?


Exemplo

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

echo json_encode($data) produz um objeto vazio:

{}

var_dump($data) no entanto, funciona como esperado:

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

Termo aditivo

1)

Portanto, esta é a toArray()função que desenvolvi para a Mf_Dataclasse:

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

No entanto, como os Mf_Dataobjetos também têm uma referência a seu objeto pai (que contém ), isso falha com a recursão. Funciona perfeitamente quando removo a _parentreferência.

2)

Apenas para acompanhar, a função final para transformar um objeto de nó de árvore complexo que eu escolhi foi:

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

Estou fazendo o acompanhamento novamente, com uma implementação um pouco mais limpa. Usar interfaces para uma instanceofverificação parece muito mais limpo do que method_exists()( no entanto method_exists(), herança / implementação de corte cruzado ).

Usar unset()parecia um pouco confuso também e parece que a lógica deveria ser refatorada em outro método. No entanto, esta implementação faz copiar a matriz de propriedade ( devido àarray_diff_key ), então algo a considerar.

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
fonte
4
+1 Boa pergunta, ainda não conhecia esse recurso.
takehin
@takeshin - Sim, a data de edição na página do documento é 4 dias atrás. Estou feliz em ver isso!
Dan Lugg
2
Para referência a outras pessoas que estão olhando para isso, json_encode pode manipular objetos muito bem. No entanto, ele codifica apenas membros públicos desse objeto. Portanto, se você tiver variáveis ​​de classe protegidas ou privadas, precisará de um dos métodos postados ou JsonSerializable.
Matthew Herbst
@MatthewHerbst Certamente. A pergunta antiga é velha agora, e <5,4 não é mais uma opção de qualquer maneira (ou pelo menos não deveria ser). DefinitivamenteJsonSerializable
Dan Lugg

Respostas:

45

editar : atualmente é 24/09/2016, e o PHP 5.4 foi lançado 01/03/2012 e o suporte terminou 01/09/2015. Ainda assim, essa resposta parece ganhar votos positivos. Se ainda estiver usando PHP <5.4, você está criando um risco de segurança e encerrando seu projeto . Se você não tem motivos convincentes para permanecer em <5.4, ou mesmo já usa a versão> = 5.4, não use esta resposta , e apenas use PHP> = 5.4 (ou, você sabe, um recente) e implemente a interface JsonSerializable


Você definiria uma função, por exemplo chamada getJsonData();, que retornaria um array, stdClassobjeto ou algum outro objeto com parâmetros visíveis em vez de parâmetros privados / protegidos, e faria a json_encode($data->getJsonData());. Em essência, implemente a função de 5.4, mas chame-a manualmente.

Algo assim funcionaria, como get_object_vars()é chamado de dentro da classe, tendo acesso a variáveis ​​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
fonte
2
Obrigado @Wrikken - Existe algum atalho para reduzir um objeto, objetos contidos nele ( todos os membros, independentemente da visibilidade ou tipo ) a uma matriz associativa, ou typecasting para stdClass? Estou pensando na direção de Reflexão , mas se não, vou apenas descobrir algo para executá-la recursivamente.
Dan Lugg
A reflexão seria o caminho mais longo. Como você está dentro da classe em sua getJsonData()função, pode simplesmente chamar get_object_vars()e percorrer esse resultado à procura de mais objetos.
Wrikken
Eu quase resolvi isso; o problema agora é a recursão. Cada objeto possui uma _parentpropriedade para que a árvore possa ser percorrida até a raiz. Veja minha edição para uma atualização; talvez eu deva fazer outra pergunta, já que esse problema foi abstraído do meu original.
Dan Lugg
Um simples unset($array['_parent']);antes da caminhada deve resolver.
Wrikken
Incrível, obrigado @Wrikken - Eu estava começando a tentar testes de igualdade complicados, passando um objeto de contexto $parentcomo dados do usuário para array_walk_recursive(). Simples é bonito! Além disso, é $array["\0class\0property"]por causa da poluição de bytes nulos porque eu estava usando conversão. Acho que vou mudar para get_object_vars().
Dan Lugg
91

Nos casos mais simples, a sugestão de tipo deve funcionar:

$json = json_encode( (array)$object );
Takehin
fonte
7
Isso fornece nomes de propriedade longos / feios se você trabalhar com namespaces e carregador automático.
BetaRide 01 de
esta é a melhor solução, precisa e concisa!
Sujal Mandal
4
existe uma maneira de obter nomes de propriedade mais limpos?
Christoffer
5
por que adiciona \ u0000 * \ u0000 no início dos nomes das propriedades?
Elia Weiss
1
Inútil com propriedades privadas. Todos vocês devem aprender sobre en.wikipedia.org/wiki/Open/closed_principle .
Fabian Picone
19

json_encode()irá codificar apenas variáveis ​​de membros públicos. então, se você quiser incluir o privado uma vez, você deve fazer isso sozinho (como os outros sugeriram)

Jfried
fonte
8

Seguir o código está fazendo o trabalho usando reflexão. Ele assume que você tem getters para as propriedades que deseja 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
fonte
1
Estou tão apaixonada por você agora! Vou te mandar um pouco de bacon, cerveja ou cupcake, que tal um cupcake?
Jonathan dos Santos
esta é uma ótima aula! também funciona com itens de objetos protegidos.
Roelof Berkepeis
2

Como seu tipo de objeto é personalizado, eu tenderia a concordar com sua solução - dividi-lo em segmentos menores usando um método de codificação (como JSON ou serializar o conteúdo) e, por outro lado, ter o código correspondente para reconstruir o objeto.

barfoon
fonte
2

Minha versão:

json_encode(self::toArray($ob))

Implementação:

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
fonte
Exatamente o que eu estava procurando. Resolve o problema com soldados. Simples e pequeno.
Fabian Picone
1

Tente usar isso, funcionou bem para mim.

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

Mude para seus tipos de variáveis privateparapublic

Isso é simples e mais legível.

Por exemplo

Não está funcionando;

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
fonte
é muito estranho. mas é verdade.
Abograph
0

Fiz uma boa classe auxiliar que converte um objeto com métodos get em um array. Não depende de propriedades, apenas métodos.

Portanto, tenho o seguinte objeto de revisão que contém dois métodos:

Reveja

  • getAmountReviews: int
  • getReviews: matriz de comentários

Comente

  • getSubject
  • getDescription

O script que escrevi irá transformá-lo em um array com propriedades parecidas com estas:

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

Fonte: PHP Serializer que converte um objeto em um array que pode ser codificado para JSON.

Tudo o que você precisa fazer é envolver json_encode em torno da saída.

Algumas informações sobre o script:

  • Apenas métodos que começam com get são adicionados
  • Métodos privados são ignorados
  • Construtor é ignorado
  • Os caracteres maiúsculos no nome do método serão substituídos por um sublinhado e um caractere minúsculo
Jamie
fonte
-7

Passei algumas horas no mesmo problema. Meu objeto a ser convertido contém muitos outros cujas definições não devo tocar (API), então eu vim com uma solução que pode ser lenta, eu acho, mas estou usando para fins de desenvolvimento.

Este converte qualquer objeto em array

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

Isso converte qualquer objeto em 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
fonte
Existe outra resposta multa e precisa, já aceita. Sua resposta acrescenta algo radicalmente diferente, mais eficiente ou compacto? Acho que não
Yaroslav,
Vou ser honesto; Não acho que isso responda à pergunta de forma alguma.
Dan Lugg de
5
Já se passaram cerca de 6 meses; Tenho retornado aqui periodicamente devido a votos positivos e para fazer algumas edições para futuros visitantes; Eu ainda não tenho idéia do que diabos isso é suposto fazer.
Dan Lugg
unlink($thisAnswer);
Dan Lugg
as pessoas tendem a votar contra o que não entendem. Pode não ser por dizer uma solução exata, mas é algo para se olhar. Nesse caso, você pede esclarecimentos antes de fazer votos negativos.
Gimali