Сериализация объекта PHP в JSON

101

Итак, я бродил по php.net в поисках информации о сериализации объектов PHP в JSON, когда наткнулся на новый интерфейс JsonSerializable . Это только PHP> = 5.4 , и я работаю в среде 5.3.x.

Как достигается такая функциональность в PHP <5.4 ?

Я еще не очень много работал с JSON, но я пытаюсь поддерживать уровень API в приложении, и сброс объекта данных ( который в противном случае был бы отправлен в представление ) в JSON был бы идеальным.

Если я попытаюсь сериализовать объект напрямую, он вернет пустую строку JSON; это потому, что я предполагаю, json_encode()что не знает, что, черт возьми, делать с объектом. Должен ли я рекурсивно уменьшить объект в массив, а затем закодировать что ?


пример

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

echo json_encode($data) создает пустой объект:

{}

var_dump($data) однако работает как ожидалось:

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

Дополнение

1)

Итак, это toArray()функция, которую я разработал для Mf_Dataкласса:

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

Однако, поскольку Mf_Dataобъекты также имеют ссылку на их родительский ( содержащий ) объект, это не удается с рекурсией. Работает как шарм, когда я удаляю _parentссылку.

2)

Чтобы продолжить, последняя функция для преобразования сложного объекта дерева-узла, с которым я работал, была:

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

Я снова продолжаю работу, немного чище реализации. Использование интерфейсов для instanceofпроверки кажется намного чище method_exists()( однако method_exists()перекрестное наследование / реализация ).

Использование unset()тоже показалось немного запутанным, и кажется, что логику следует реорганизовать в другой метод. Тем не менее, эта реализация делает копирование массива свойства ( из - заarray_diff_key ), так что - то рассмотреть.

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 Хороший вопрос, еще не знал об этой функции.
takehin
@takeshin - Да, дата редактирования на странице документа 4 дня назад. Я рада это видеть!
Дэн Лагг
2
Для справки о других, смотрящих на это, json_encode может нормально обрабатывать объекты. Однако он кодирует только общедоступные члены этого объекта. Поэтому, если у вас есть защищенные или частные переменные класса, вам понадобится либо один из опубликованных методов, либо JsonSerializable.
Мэтью Хербст,
@MatthewHerbst Конечно. Старый вопрос сейчас старый, и <5.4 на самом деле больше не вариант (или, по крайней мере, не должен быть) ОпределенноJsonSerializable
Дэн Лагг

Ответы:

45

изменить : в настоящее время 2016-09-24, а PHP 5.4 был выпущен 2012-03-01, и поддержка закончилась 2015-09-01. Тем не менее, этот ответ, похоже, набирает голоса. Если вы все еще используете PHP <5.4, вы создаете угрозу безопасности и ставите под угрозу свой проект . Если у вас нет веских причин оставаться на <5.4 или даже уже использовать версию> = 5.4, не используйте этот ответ , а просто используйте PHP> = 5.4 (или, вы знаете, недавний) и реализуйте интерфейс JsonSerializable.


Вы должны определить функцию, например, с именем getJsonData();, которая будет возвращать либо массив, stdClassобъект или какой-либо другой объект с видимыми параметрами, а не частными / защищенными, и выполните json_encode($data->getJsonData());. По сути, реализовать функцию из 5.4, но вызывать ее вручную.

Что-то вроде этого будет работать, как get_object_vars()вызывается изнутри класса, имея доступ к закрытым / защищенным переменным:

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
источник
2
Спасибо @Wrikken - есть ли какой-либо ярлык для сокращения объекта, содержащихся в нем объектов ( всех членов, независимо от видимости или типа ) до ассоциативного массива или приведения его к типуstdClass ? Я думаю в направлении Reflection , но если нет, я просто придумаю что-нибудь, чтобы рекурсивно это выполнить.
Дэн Лагг
Отражение было бы долгим путем. Поскольку вы находитесь внутри класса своей getJsonData()функции, вы можете просто вызвать get_object_vars()и просмотреть этот результат в поисках дополнительных объектов.
Wrikken
Я почти разобрался с этим; теперь проблема в рекурсии. У каждого объекта есть _parentсвойство, поэтому по дереву можно пройти до корня. Смотрите мое редактирование для обновления; возможно, мне следует задать другой вопрос, поскольку эта проблема теперь абстрагирована от моего оригинала.
Дэн Лагг
Простое упражнение unset($array['_parent']);перед прогулкой должно помочь.
Wrikken
Замечательно, спасибо @Wrikken - я начинал пробовать сложные тесты на равенство, передавая объект контекста $parentкак пользовательские данные array_walk_recursive(). Просто красиво! Кроме того, это $array["\0class\0property"]из-за загрязнения нулевым байтом, потому что я использовал кастинг. Думаю перейду на get_object_vars().
Дэн Лагг
91

В простейших случаях должна работать подсказка типов:

$json = json_encode( (array)$object );
Takehin
источник
7
Это дает длинные и уродливые имена свойств, если вы работаете с пространствами имен и автозагрузчиком.
BetaRide 01
это лучшее решение, точное и лаконичное!
Суджал Мандал
4
есть ли способ получить более чистые имена свойств?
Christoffer
5
почему он добавляет \ u0000 * \ u0000 в начало имен свойств?
Элия ​​Вайс
1
Бесполезно с частной собственностью. Вы все должны узнать о en.wikipedia.org/wiki/Open/closed_principle .
Фабиан Пиконе
19

json_encode()будет кодировать только общедоступные переменные-члены. поэтому, если вы хотите включить частное, когда вам нужно сделать это самостоятельно (как предлагали другие)

Джфрид
источник
9

Следующий код выполняет свою работу с использованием отражения. Предполагается, что у вас есть геттеры для свойств, которые вы хотите сериализовать.

    <?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
Я так тебя люблю прямо сейчас! Я пришлю тебе бекона, пива или кекса, а как насчет кекса?
Джонатан дос Сантос,
это отличный класс! он также работает с объектами защищенных объектов.
Roelof Berkepeis
2

Поскольку ваш тип объекта является индивидуальным, я бы склонен согласиться с вашим решением - разбейте его на более мелкие сегменты, используя метод кодирования (например, JSON или сериализацию контента), а на другом конце - соответствующий код для воссоздания объекта.

барфун
источник
2

Моя версия:

json_encode(self::toArray($ob))

Реализация:

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

Джон Племя
источник
Именно то, что я искал. Решает проблему с рядовым. Простой и маленький.
Фабиан Пиконе
1

Попробуйте использовать это, у меня это сработало.

json_encode(unserialize(serialize($array)));
Наванит Мохан
источник
1

Измените типы переменных privateнаpublic

Это просто и удобнее для чтения.

Например

Не работает;

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

Это работает;

class A{
   public $var1="valuevar1";
   public $var2="valuevar2";
   public function tojson(){
    return json_encode($this)
   }
}
Ферхат КОГЕР
источник
это очень странно. но это правда.
Abilogos
0

Я сделал хороший вспомогательный класс, который преобразует объект с методами get в массив. Он не полагается на свойства, а только на методы.

Итак, у меня есть следующий объект обзора, который содержит два метода:

Обзор

  • getAmountReviews: int
  • getReviews: массив комментариев

Комментарий

  • getSubject
  • getDescription

Написанный мной скрипт преобразует его в массив со свойствами, которые выглядят так:

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

Источник: сериализатор PHP, который преобразует объект в массив, который можно закодировать в JSON.

Все, что вам нужно сделать, это обернуть json_encode вокруг вывода.

Немного информации о скрипте:

  • Добавляются только методы, которые начинаются с get
  • Частные методы игнорируются
  • Конструктор игнорируется
  • Заглавные символы в имени метода будут заменены символом подчеркивания и нижним регистром.
Джейми
источник
-7

Я потратил несколько часов на решение той же проблемы. Мой объект для преобразования содержит множество других, определения которых я не должен трогать (API), поэтому я придумал решение, которое, я полагаю, может быть медленным, но я использую его для целей разработки.

Преобразует любой объект в массив

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

Это преобразует любой объект в 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
источник
Есть еще один прекрасный и точный ответ, уже принятый. Ваш ответ добавляет что-то принципиально иное, более эффективное или компактное? Наверное, нет
Ярослав
Я буду честен; Я не думаю, что это вообще ответ на вопрос.
Дэн Лагг,
5
Прошло около 6 месяцев; Я периодически возвращался сюда из-за голосов «за» и для внесения некоторых изменений для будущих посетителей; Я до сих пор понятия не имею, что это, черт возьми, должно делать.
Дэн Лагг
unlink($thisAnswer);
Дэн Лагг
1
inline php strings, eval, shell_exec(php)... куб.см-комбо.
vp_arth