Serializacja obiektu PHP do formatu JSON

101

Więc byłem wędrówki php.net informacji na temat szeregowania PHP obiektów do JSON, gdy natknąłem się nowym interfejsem JsonSerializable . Jest to jednak tylko PHP> = 5.4 i pracuję w środowisku 5.3.x.

W jaki sposób osiągnięto taką funkcjonalność PHP <5.4 ?

Nie pracowałem jeszcze zbyt wiele z JSON, ale próbuję obsługiwać warstwę API w aplikacji i zrzucenie obiektu danych ( który w przeciwnym razie zostałby wysłany do widoku ) do JSON byłoby idealne.

Jeśli spróbuję bezpośrednio serializować obiekt, zwraca on pusty ciąg JSON; a to dlatego, że zakładam, json_encode()że nie wie, co do cholery zrobić z obiektem. Powinienem rekurencyjnie zmniejszyć obiekt do tablicy, a następnie zakodować , że ?


Przykład

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

echo json_encode($data) tworzy pusty obiekt:

{}

var_dump($data) jednak działa zgodnie z oczekiwaniami:

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

Uzupełnienie

1)

Oto toArray()funkcja, którą wymyśliłem dla Mf_Dataklasy:

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

Jednak ponieważ Mf_Dataobiekty mają również odniesienie do ich obiektu nadrzędnego ( zawierającego ), kończy się to niepowodzeniem w przypadku rekursji. Działa jak urok, kiedy usuwam _parentodniesienie.

2)

Aby kontynuować, ostatnią funkcją do przekształcenia złożonego obiektu z węzłem drzewa, z którym poszedłem, było:

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

Kontynuuję ponownie, z nieco czystszą implementacją. Używanie interfejsów do instanceofsprawdzenia wydaje się znacznie czystsze niż method_exists()( jednak method_exists()dziedziczenie / implementacja krzyżowa ).

Używanie również unset()wydawało się nieco kłopotliwe i wydaje się, że logikę należy przełożyć na inną metodę. Jednak ta implementacja ma skopiować tablicę posesji ( z powoduarray_diff_key ), więc coś do rozważenia.

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
źródło
4
+1 Ładne pytanie, jeszcze nie znałam tej funkcji.
takeshin
@takeshin - Yeop, data edycji na stronie dokumentu to 4 dni temu. Cieszę się, że to widzę!
Dan Lugg
2
W odniesieniu do innych osób, które to widzą, json_encode może dobrze obsługiwać obiekty. Jednak koduje tylko publicznych członków tego obiektu. Więc jeśli masz chronione lub prywatne zmienne klasowe, potrzebujesz jednej z opublikowanych metod lub JsonSerializable.
Matthew Herbst
@MatthewHerbst Oczywiście. Stare pytanie jest teraz stare, a <5.4 nie jest już tak naprawdę opcją (a przynajmniej nie powinno być) ZdecydowanieJsonSerializable
Dan Lugg

Odpowiedzi:

45

edycja : obecnie jest 2016-09-24, a PHP 5.4 zostało wydane 2012-03-01, a wsparcie zakończyło się 2015-09-01. Wydaje się jednak, że ta odpowiedź zyskuje uznanie. Jeśli nadal używasz PHP <5.4, stwarzasz zagrożenie bezpieczeństwa i zagrażasz projektowi . Jeśli nie masz przekonujących powodów, aby pozostać przy <5.4, a nawet już używasz wersji> = 5.4, nie używaj tej odpowiedzi i po prostu użyj PHP> = 5.4 (lub, wiesz, najnowszego) i zaimplementuj interfejs JsonSerializable


Zdefiniowałbyś funkcję, na przykład nazwaną getJsonData();, która zwróciłaby tablicę, stdClassobiekt lub inny obiekt z widocznymi parametrami, a nie prywatnymi / chronionymi, i zrobiłby a json_encode($data->getJsonData());. Zasadniczo zaimplementuj funkcję z 5.4, ale wywołaj ją ręcznie.

Coś takiego zadziałałoby, jak get_object_vars()jest wywoływane z wnętrza klasy, mając dostęp do prywatnych / chronionych zmiennych:

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
źródło
2
Dzięki @Wrikken - czy istnieje jakiś skrót do redukcji obiektu, obiektów w nim zawartych ( wszystkich elementów członkowskich niezależnie od widoczności lub typu ) do tablicy asocjacyjnej lub przypisania jej do typu stdClass? Myślę w kierunku Odbicia , ale jeśli nie, po prostu wymyślę coś, aby wykonać to rekurencyjnie.
Dan Lugg
Refleksja to długa droga. Ponieważ jesteś wewnątrz klasy swojej getJsonData()funkcji, możesz po prostu wywołać get_object_vars()i przejrzeć ten wynik, szukając więcej obiektów.
Wrikken
Prawie to załatwiłem; teraz problemem jest rekurencja. Każdy obiekt ma _parentwłaściwość, dzięki czemu drzewo można przejść do katalogu głównego. Zobacz moją edycję dla aktualizacji; być może powinienem zadać inne pytanie, ponieważ ta kwestia jest teraz oderwana od mojego oryginału.
Dan Lugg
Prosty unset($array['_parent']);przed spacer powinien załatwić sprawę.
Wrikken
Świetnie, dzięki @Wrikken - zacząłem próbować skomplikowanych testów równości, przekazując obiekt kontekstu $parentjako dane użytkownika array_walk_recursive(). Prostota jest piękna! Poza tym jest to $array["\0class\0property"]spowodowane zanieczyszczeniem zerobajtowym, ponieważ używałem odlewania. Myślę, że przestawię się na get_object_vars().
Dan Lugg
91

W najprostszych przypadkach podpowiedź typu powinna działać:

$json = json_encode( (array)$object );
takeshin
źródło
7
Daje to długie / brzydkie nazwy właściwości, jeśli pracujesz z przestrzeniami nazw i automatycznym ładowaniem.
BetaRide
to najlepsze rozwiązanie, precyzyjne i zwięzłe!
Sujal Mandal
4
czy istnieje sposób na uzyskanie czystszych nazw nieruchomości?
Christoffer
5
dlaczego dodaje \ u0000 * \ u0000 na początku nazw rekwizytów?
Elia Weiss
1
Bezużyteczne w przypadku własności prywatnych. Wszyscy powinniście dowiedzieć się więcej o en.wikipedia.org/wiki/Open/closed_principle .
Fabian Picone
19

json_encode()zakoduje tylko publiczne zmienne składowe. więc jeśli chcesz dołączyć prywatny, gdy musisz to zrobić sam (jak sugerowali inni)

jfried
źródło
9

Poniższy kod wykonuje zadanie za pomocą refleksji. Zakłada się, że masz metody pobierające dla właściwości, które chcesz serializować

    <?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
źródło
1
Jestem teraz w tobie tak bardzo zakochany! Przyślę ci bekon, piwo albo babeczkę. A co z babeczką?
Jonathan dos Santos
to jest wielka klasa! działa również z obiektami chronionymi.
Roelof Berkepeis
2

Ponieważ typ twojego obiektu jest niestandardowy, zwykle zgadzam się z twoim rozwiązaniem - podziel je na mniejsze segmenty za pomocą metody kodowania (takiej jak JSON lub serializacja treści), a na drugim końcu mam odpowiedni kod, aby ponownie skonstruować obiekt.

barfoon
źródło
2

Moja wersja:

json_encode(self::toArray($ob))

Realizacja:

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
źródło
Dokładnie to, czego szukałem. Rozwiązuje problem z szeregowymi. Prosty i mały.
Fabian Picone
1

Spróbuj tego użyć, to zadziałało dobrze dla mnie.

json_encode(unserialize(serialize($array)));
Navaneeth Mohan
źródło
1

Zmień typy zmiennych privatenapublic

To jest proste i bardziej czytelne.

Na przykład

Nie działa;

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

To działa;

class A{
   public $var1="valuevar1";
   public $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}
Ferhat KOÇER
źródło
to bardzo dziwne. ale to prawda.
Abilogos
0

Zrobiłem ładną klasę pomocniczą, która konwertuje obiekt za pomocą metod get na tablicę. Nie opiera się na właściwościach, tylko na metodach.

Mam więc następujący obiekt przeglądu, który zawiera dwie metody:

Przejrzeć

  • getAmountReviews: int
  • getReviews: tablica komentarzy

Komentarz

  • getSubject
  • getDescription

Napisany przeze mnie skrypt przekształci go w tablicę z właściwościami, które wyglądają następująco:

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

Źródło: serializator PHP, który konwertuje obiekt na tablicę, którą można zakodować do formatu JSON.

Wszystko, co musisz zrobić, to owinąć dane wyjściowe json_encode.

Kilka informacji o skrypcie:

  • Dodawane są tylko metody zaczynające się od get
  • Metody prywatne są ignorowane
  • Konstruktor jest ignorowany
  • Wielkie znaki w nazwie metody zostaną zastąpione podkreśleniem i małą literą
Jamie
źródło
-7

Spędziłem kilka godzin nad tym samym problemem. Mój obiekt do konwersji zawiera wiele innych, których definicji nie powinienem dotykać (API), więc wymyśliłem rozwiązanie, które może być powolne, ale używam go do celów programistycznych.

Ten konwertuje dowolny obiekt na tablicę

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

To konwertuje dowolny obiekt na 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
źródło
Jest jeszcze jedna dobra i dokładna odpowiedź, już zaakceptowana. Czy Twoja odpowiedź dodaje coś radykalnie innego, wydajniejszego lub bardziej kompaktowego? Chyba nie
Jarosław
Mam zamiar być szczery; Myślę, że to w ogóle nie odpowiada na pytanie.
Dan Lugg,
5
Minęło około 6 miesięcy; Od czasu do czasu tu wracałem ze względu na głosy za i aby dokonać pewnych zmian dla przyszłych gości; I nadal nie mają pojęcia, co to do cholery ma robić.
Dan Lugg
unlink($thisAnswer);
Dan Lugg
1
inline php strings, eval, shell_exec(php)... cc-kombi.
vp_arth