PHP-Objekt für JSON serialisieren

101

Als ich über die neue JsonSerializable-Schnittstelle stolperte, suchte ich in php.net nach Informationen zum Serialisieren von PHP-Objekten in JSON . Es ist jedoch nur PHP> = 5.4 und ich arbeite in einer 5.3.x-Umgebung.

Wie wird diese Art von Funktionalität erreicht PHP <5.4 ?

Ich habe noch nicht viel mit JSON gearbeitet, aber ich versuche, eine API-Schicht in einer Anwendung zu unterstützen, und das Dumping des Datenobjekts ( das sonst an die Ansicht gesendet würde) in JSON wäre perfekt.

Wenn ich versuche, das Objekt direkt zu serialisieren, wird eine leere JSON-Zeichenfolge zurückgegeben. Das liegt daran, dass ich vermutlich json_encode()nicht weiß, was zum Teufel mit dem Objekt zu tun ist. Sollte ich das Objekt in ein Array rekursiv reduzieren, und dann codieren , dass ?


Beispiel

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

echo json_encode($data) erzeugt ein leeres Objekt:

{}

var_dump($data) funktioniert jedoch wie erwartet:

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

Nachtrag

1)

Das ist also die toArray()Funktion, die ich für die Mf_DataKlasse entwickelt habe:

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

Da die Mf_DataObjekte jedoch auch einen Verweis auf ihr übergeordnetes ( enthaltendes ) Objekt haben, schlägt dies mit der Rekursion fehl. Funktioniert jedoch wie ein Zauber, wenn ich die _parentReferenz entferne .

2)

Die letzte Funktion zum Transformieren eines komplexen Baumknotenobjekts, mit dem ich mich befasst habe, war:

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

Ich folge wieder mit einer etwas saubereren Implementierung. Die Verwendung von Schnittstellen für eine instanceofÜberprüfung scheint viel sauberer zu sein als method_exists()( jedoch method_exists()übergreifende Vererbung / Implementierung ).

Die Verwendung unset()schien auch etwas chaotisch zu sein, und es scheint, dass die Logik in eine andere Methode umgestaltet werden sollte. Allerdings ist diese Implementierung hat die Eigenschaft Array (Kopie wegenarray_diff_key ), so etwas zu prüfen.

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
quelle
4
+1 Schöne Frage, kannte diese Funktion noch nicht.
Takeshin
@takeshin - Ja, das Bearbeitungsdatum auf der Dokumentseite ist 4 Tage her. Ich bin froh es zu sehen!
Dan Lugg
2
Als Hinweis auf andere, die dies betrachten, kann json_encode Objekte problemlos verarbeiten. Es werden jedoch nur öffentliche Mitglieder dieses Objekts codiert. Wenn Sie also geschützte oder private Klassenvariablen haben, benötigen Sie entweder eine der veröffentlichten Methoden oder JsonSerializable.
Matthew Herbst
@ MatthewHerbst Sicher. Alte Frage ist jetzt alt, und <5.4 ist sowieso keine Option mehr (oder sollte es zumindest nicht sein) DefinitivJsonSerializable
Dan Lugg

Antworten:

45

Bearbeiten : Es ist derzeit 24.09.2016 und PHP 5.4 wurde am 01.03.2012 veröffentlicht und der Support wurde am 01.09.2015 beendet . Dennoch scheint diese Antwort positive Stimmen zu erhalten. Wenn Sie immer noch PHP <5.4 verwenden, schaffen Sie ein Sicherheitsrisiko und gefährden Ihr Projekt . Wenn Sie keine zwingenden Gründe haben, bei <5.4 zu bleiben oder sogar bereits Version> = 5.4 zu verwenden, verwenden Sie diese Antwort nicht und verwenden Sie einfach PHP> = 5.4 (oder, wie Sie wissen, eine neuere) und implementieren Sie die JsonSerializable-Schnittstelle


Sie würden eine Funktion definieren, zum Beispiel einen Namen getJsonData();, die entweder ein Array, ein stdClassObjekt oder ein anderes Objekt mit sichtbaren Parametern anstelle von privaten / geschützten zurückgibt, und a json_encode($data->getJsonData());. Implementieren Sie die Funktion im Wesentlichen ab 5.4, rufen Sie sie jedoch von Hand auf.

So etwas würde funktionieren, wie get_object_vars()es innerhalb der Klasse aufgerufen wird, und Zugriff auf private / geschützte Variablen haben:

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
quelle
2
Danke @Wrikken - Gibt es eine Verknüpfung zum Reduzieren eines Objekts, der darin enthaltenen Objekte ( alle Mitglieder unabhängig von Sichtbarkeit oder Typ ) auf ein assoziatives Array oder zum Typisieren stdClass? Ich denke in Richtung Reflexion , aber wenn nicht, werde ich einfach etwas herausfinden, um es rekursiv auszuführen.
Dan Lugg
Reflexion wäre der lange Weg. Wenn Sie sich in Ihrer Funktion innerhalb der Klasse befinden getJsonData(), können Sie get_object_vars()dieses Ergebnis einfach aufrufen und durchlaufen, um nach weiteren Objekten zu suchen.
Wrikken
Ich habe es fast geklärt; Das Problem ist jetzt die Rekursion. Jedes Objekt verfügt über eine _parentEigenschaft, sodass der Baum zur Wurzel durchlaufen werden kann. Siehe meine Bearbeitung für ein Update; Vielleicht sollte ich eine andere Frage stellen, da diese Ausgabe jetzt von meinem Original abstrahiert ist.
Dan Lugg
Ein einfacher unset($array['_parent']);vor dem Spaziergang sollte den Trick machen.
Wrikken
Super, danke @Wrikken - Ich habe angefangen, komplizierte Gleichheitstests zu versuchen und ein Kontextobjekt $parentals Benutzerdaten an übergeben array_walk_recursive(). Einfach ist schön! Es liegt auch an $array["\0class\0property"]der Null-Byte-Verschmutzung, weil ich Casting verwendet habe. Ich denke, ich werde zu wechseln get_object_vars().
Dan Lugg
91

In den einfachsten Fällen sollte ein Tipp funktionieren:

$json = json_encode( (array)$object );
Takeshin
quelle
7
Dies führt zu langwierigen / hässlichen Eigenschaftsnamen, wenn Sie mit Namespaces und Auto Loader arbeiten.
BetaRide
Dies ist die beste Lösung, präzise und präzise!
Sujal Mandal
4
Gibt es eine Möglichkeit, sauberere Eigenschaftsnamen zu erhalten?
Christoffer
5
Warum fügt es am Anfang von Requisitennamen \ u0000 * \ u0000 hinzu?
Elia Weiss
1
Nutzlos mit Privateigentum. Sie alle sollten etwas über en.wikipedia.org/wiki/Open/closed_principle lernen .
Fabian Picone
19

json_encode()codiert nur öffentliche Mitgliedsvariablen. Wenn Sie also das Private einbeziehen möchten, müssen Sie es selbst tun (wie von den anderen vorgeschlagen).

jfried
quelle
8

Der folgende Code erledigt die Arbeit mit Reflektion. Es wird davon ausgegangen, dass Sie Getter für die Eigenschaften haben, die Sie serialisieren möchten

    <?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
quelle
1
Ich bin gerade so in dich verliebt! Ich schicke dir Speck oder Bier oder einen Cupcake. Was ist mit einem Cupcake?
Jonathan dos Santos
Das ist eine großartige Klasse! Es funktioniert auch mit geschützten Objektelementen.
Roelof Berkepeis
2

Da Ihr Objekttyp benutzerdefiniert ist, stimme ich Ihrer Lösung eher zu - zerlegen Sie sie mithilfe einer Codierungsmethode (wie JSON oder Serialisierung des Inhalts) in kleinere Segmente und verfügen Sie am anderen Ende über entsprechenden Code, um das Objekt neu zu erstellen.

Barfoon
quelle
2

Meine Version:

json_encode(self::toArray($ob))

Implementierung:

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
quelle
Genau das, wonach ich gesucht habe. Löst Probleme mit Privaten. Einfach und klein.
Fabian Picone
1

Versuchen Sie es damit, das hat bei mir gut funktioniert.

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

Wechseln Sie zu Ihren Variablentypen privatezupublic

Dies ist einfach und besser lesbar.

Beispielsweise

Funktioniert nicht;

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

Es funktioniert;

class A{
   public $var1="valuevar1";
   public $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}
Ferhat KOÇER
quelle
Es ist sehr seltsam. aber es ist wahr.
Abilogos
0

Ich habe eine nette Hilfsklasse erstellt, die ein Objekt mit get-Methoden in ein Array konvertiert. Es beruht nicht auf Eigenschaften, sondern nur auf Methoden.

Ich habe also das folgende Überprüfungsobjekt, das zwei Methoden enthält:

Rezension

  • getAmountReviews: int
  • getReviews: Array von Kommentaren

Kommentar

  • getSubject
  • getDescription

Das Skript, das ich geschrieben habe, verwandelt es in ein Array mit Eigenschaften, die wie folgt aussehen:

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

Quelle: PHP Serializer, der ein Objekt in ein Array konvertiert, das in JSON codiert werden kann.

Alles was Sie tun müssen, ist json_encode um die Ausgabe zu wickeln.

Einige Informationen zum Skript:

  • Es werden nur Methoden hinzugefügt, die mit get beginnen
  • Private Methoden werden ignoriert
  • Konstruktor wird ignoriert
  • Großbuchstaben im Methodennamen werden durch einen Unterstrich und ein Kleinbuchstaben ersetzt
Jamie
quelle
-7

Ich habe einige Stunden mit dem gleichen Problem verbracht. Mein zu konvertierendes Objekt enthält viele andere, deren Definitionen ich nicht berühren soll (API). Daher habe ich eine Lösung gefunden, die möglicherweise langsam ist, aber ich verwende sie für Entwicklungszwecke.

Dieser konvertiert jedes Objekt in ein 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;
}

Dadurch wird jedes Objekt in stdClass konvertiert

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
quelle
Es gibt eine weitere gute und genaue Antwort, die bereits akzeptiert wurde. Fügt Ihre Antwort etwas radikal anderes, effizienteres oder kompakteres hinzu? Ich denke nicht
Jaroslaw
Ich werde ehrlich sein; Ich denke, das beantwortet die Frage überhaupt nicht.
Dan Lugg
5
Es ist ungefähr 6 Monate her; Ich bin aufgrund von Gegenstimmen regelmäßig hierher zurückgekehrt, um einige Änderungen für zukünftige Besucher vorzunehmen. Ich habe immer noch keine Ahnung, was zum Teufel das tun soll.
Dan Lugg
unlink($thisAnswer);
Dan Lugg
Menschen neigen dazu, das, was sie nicht verstehen, herabzustimmen. Es ist vielleicht keine exakte Lösung, aber es ist etwas, das untersucht werden muss. In diesem Fall bitten Sie vor Abstimmungen um Klarstellung.
Gimali