Serializzazione di un oggetto PHP in JSON


101

Quindi stavo girovagando per php.net per informazioni sulla serializzazione di oggetti PHP in JSON, quando mi sono imbattuto nella nuova interfaccia JsonSerializable . Tuttavia è solo PHP> = 5.4 , e sto funzionando in un ambiente 5.3.x.

In che modo questo tipo di funzionalità ha raggiunto PHP <5.4 ?

Non ho ancora lavorato molto con JSON, ma sto cercando di supportare un livello API in un'applicazione e il dumping dell'oggetto dati ( che altrimenti verrebbe inviato alla vista ) in JSON sarebbe perfetto.

Se provo a serializzare direttamente l'oggetto, restituisce una stringa JSON vuota; il che è perché presumo json_encode()non sappia cosa diavolo fare con l'oggetto. Devo ricorsivamente ridurre l'oggetto in una matrice, e quindi codificare che ?


Esempio

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

echo json_encode($data) produce un oggetto vuoto:

{}

var_dump($data) tuttavia, funziona come previsto:

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

appendice

1)

Quindi questa è la toArray()funzione che ho ideato per 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;
}

Tuttavia, poiché gli Mf_Dataoggetti hanno anche un riferimento al loro oggetto genitore ( contenente ), ciò non riesce con la ricorsione. Funziona come un fascino anche se quando rimuovo il _parentriferimento.

2)

Solo per proseguire, la funzione finale per trasformare un complesso oggetto nodo ad albero con cui sono andato è stata:

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

Sto seguendo di nuovo, con un'implementazione un po 'più pulita. L'uso delle interfacce per un instanceofcontrollo sembra molto più pulito di method_exists()( tuttavia method_exists(), ereditarietà / implementazione incrociata ).

Anche l'uso unset()sembrava un po 'complicato e sembra che la logica debba essere riformulata in un altro metodo. Tuttavia, questa implementazione non copiare la matrice di proprietà ( a causa diarray_diff_key ), quindi una cosa da considerare.

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 Bella domanda, non conoscevo ancora questa funzione.
Takehin

@takeshin - Già, la data di modifica nella pagina del documento è di 4 giorni fa. Sono contento di vederlo!
Dan Lugg

2
Per riferimento ad altri che stanno guardando questo, json_encode può gestire bene gli oggetti. Tuttavia, codifica solo i membri pubblici di quell'oggetto. Quindi, se hai variabili di classe protette o private, allora hai bisogno di uno dei metodi pubblicati o di JsonSerializable.
Matthew Herbst

@MatthewHerbst Certamente. La vecchia domanda è vecchia ora, e <5.4 non è più un'opzione comunque (o almeno non dovrebbe esserlo) SicuramenteJsonSerializable
Dan Lugg

Risposte:


45

modifica : è attualmente il 24/09/2016 e PHP 5.4 è stato rilasciato 01/03/2012 e il supporto è terminato il 01/09/2015. Tuttavia, questa risposta sembra guadagnare voti positivi. Se stai ancora utilizzando PHP <5.4, stai creando un rischio per la sicurezza e compromettendo il tuo progetto . Se non hai motivi validi per rimanere a <5.4, o anche se usi già la versione> = 5.4, non usare questa risposta e usa semplicemente PHP> = 5.4 (o, sai, uno recente) e implementa l'interfaccia JsonSerializable


Definiresti una funzione, ad esempio named getJsonData();, che restituirebbe un array, un stdClassoggetto o qualche altro oggetto con parametri visibili invece di quelli privati ​​/ protetti, e faresti un file json_encode($data->getJsonData());. In sostanza, implementa la funzione da 5.4, ma chiamala a mano.

Qualcosa del genere funzionerebbe, come get_object_vars()viene chiamato dall'interno della classe, avendo accesso a variabili private / protette:

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
Grazie @Wrikken - Esiste una scorciatoia per ridurre un oggetto, gli oggetti in esso contenuti ( tutti i membri indipendentemente dalla visibilità o dal tipo ) a un array associativo o per il typecasting stdClass? Sto pensando in direzione di Reflection , ma in caso contrario, troverò qualcosa per eseguirlo in modo ricorsivo.
Dan Lugg

La riflessione sarebbe la strada più lunga. Dato che sei all'interno della classe nella tua getJsonData()funzione, potresti semplicemente chiamare get_object_vars()e scorrere quel risultato alla ricerca di più oggetti.
Wrikken

L'ho quasi risolto; il problema ora è la ricorsione. Ogni oggetto ha una _parentproprietà in modo che l'albero possa essere attraversato alla radice. Vedere la mia modifica per un aggiornamento; forse dovrei fare un'altra domanda poiché questo problema è ora astratto dal mio originale.
Dan Lugg

Un semplice unset($array['_parent']);prima della passeggiata dovrebbe fare il trucco.
Wrikken

Fantastico, grazie @Wrikken: stavo iniziando a provare complicati test di uguaglianza, passando un oggetto contesto $parentcome dati utente a array_walk_recursive(). Semplice è bello! Inoltre, è a $array["\0class\0property"]causa dell'inquinamento di byte nulli perché stavo usando il casting. Penso che passerò a get_object_vars().
Dan Lugg

91

Nei casi più semplici i suggerimenti sul tipo dovrebbero funzionare:

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

7
Questo dà nomi di proprietà prolissi / brutti se lavori con spazi dei nomi e caricatore automatico.
BetaRide

questa è la soluzione migliore, precisa e concisa!
Sujal Mandal

4
c'è un modo per ottenere nomi di proprietà più puliti?
Christoffer

5
perché aggiunge \ u0000 * \ u0000 all'inizio dei nomi delle prop?
Elia Weiss

1
Inutile con proprietà private. Dovreste tutti conoscere en.wikipedia.org/wiki/Open/closed_principle .
Fabian Picone

19

json_encode()codificherà solo le variabili membro pubbliche. quindi se vuoi includere il privato una volta devi farlo da solo (come suggerito dagli altri)


8

Il codice seguente sta facendo il lavoro usando la riflessione. Si presuppone che siano disponibili getter per le proprietà che si desidera serializzare

    <?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
Sono così innamorato di te adesso! Ti mando un po 'di pancetta o birra o un cupcake e un cupcake?
Jonathan dos Santos

questa è una grande classe! funziona anche con oggetti oggetto protetti.
Roelof Berkepeis


2

Poiché il tuo tipo di oggetto è personalizzato, tenderei a concordare con la tua soluzione: suddividerlo in segmenti più piccoli utilizzando un metodo di codifica (come JSON o serializzare il contenuto) e, dall'altra parte, avere il codice corrispondente per ricostruire l'oggetto.


2

La mia versione:

json_encode(self::toArray($ob))

Implementazione:

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


Esattamente quello che stavo cercando. Risolve il problema con i privati. Semplice e piccolo.
Fabian Picone

1

Prova a usarlo, ha funzionato bene per me.

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

1

Passa ai tipi di variabile privateinpublic

Questo è semplice e più leggibile.

Per esempio

Non funziona;

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

Funziona;

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

è molto strano. ma è vero.
Abilogos

0

Ho creato una bella classe helper che converte un oggetto con metodi get in un array. Non si basa su proprietà, solo metodi.

Quindi ho il seguente oggetto di revisione che contiene due metodi:

Revisione

  • getAmountReviews: int
  • getReviews: array di commenti

Commento

  • getSubject
  • getDescription

Lo script che ho scritto lo trasformerà in un array con proprietà che assomigliano a questo:

    {
      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 che converte un oggetto in un array che può essere codificato in JSON.

Tutto quello che devi fare è avvolgere json_encode attorno all'output.

Alcune informazioni sullo script:

  • Vengono aggiunti solo i metodi che iniziano con get
  • I metodi privati ​​vengono ignorati
  • Il costruttore viene ignorato
  • I caratteri maiuscoli nel nome del metodo verranno sostituiti con un trattino basso e un carattere minuscolo

-7

Ho passato alcune ore sullo stesso problema. Il mio oggetto da convertire ne contiene molti altri le cui definizioni non dovrei toccare (API), quindi ho trovato una soluzione che potrebbe essere lenta immagino, ma la sto usando per scopi di sviluppo.

Questo converte qualsiasi oggetto in 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;
}

Questo converte qualsiasi oggetto in 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;
}

C'è un'altra risposta fine e precisa, già accettata. La tua risposta aggiunge qualcosa di radicalmente diverso, più efficiente o compatto? Immagino di no
Yaroslav

Sarò onesto; Non credo che questo risponda affatto alla domanda.
Dan Lugg

5
Sono passati circa 6 mesi; Sono periodicamente tornato qui a causa di voti positivi e per apportare alcune modifiche per futuri visitatori; Non ho ancora idea di cosa diavolo dovrebbe fare.
Dan Lugg

unlink($thisAnswer);
Dan Lugg

le persone tendono a sottovalutare ciò che non capiscono. Potrebbe non essere per dire una soluzione esatta, ma è qualcosa da esaminare. In tal caso chiedi chiarimenti prima di downvotes.
Gimali
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.