【问题标题】:Serializing PHP object to JSON将 PHP 对象序列化为 JSON
【发布时间】:2011-10-13 18:37:07
【问题描述】:

所以当我偶然发现新的JsonSerializable Interface 时,我在php.net 中寻找有关将PHP 对象序列化为JSON 的信息。不过它只是 PHP >= 5.4,而且我在 5.3.x 环境中运行。

这种功能是如何实现的PHP ?

我还没有过多地使用 JSON,但我正在尝试在应用程序中支持 API 层,并将数据对象(否则会发送到视图)转储到JSON 将是完美的。

如果我尝试直接序列化对象,它会返回一个空的 JSON 字符串;这是因为我假设json_encode() 不知道该对象到底要做什么。我是否应该递归地将对象简化为一个数组,然后对 that 进行编码?


示例

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

这就是我为Mf_Data 类设计的toArray() 函数:

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

}

【问题讨论】:

  • +1 好问题,还不知道这个功能。
  • @takeshin - 是的,文档页面上的编辑日期是 4 天前。我很高兴看到它!
  • 供其他人参考,json_encode 可以很好地处理对象。但是,它只编码该对象的公共成员。因此,如果您有受保护或私有类变量,那么您需要发布的方法之一或 JsonSerializable。
  • @MatthewHerbst 当然可以。旧问题现在已经过时了,无论如何 JsonSerializable

标签: php object json object-serialization


【解决方案1】:

由于您的对象类型是自定义的,我倾向于同意您的解决方案 - 使用编码方法(如 JSON 或序列化内容)将其分解为更小的部分,并在另一端有相应的代码来重新构建对象。

【讨论】:

    【解决方案2】:

    编辑:目前是 2016-09-24,PHP 5.4 已于 2012-03-01 发布,支持已于 2015-09-01结束。尽管如此,这个答案似乎还是获得了支持。如果您仍在使用 PHP 您正在制造安全风险并危及您的项目。如果您没有令人信服的理由留在 = 5.4,不要使用此答案,而只需使用 PHP>= 5.4(或者,您知道,最近的一个) ) 并实现the JsonSerializable interface


    您将定义一个函数,例如名为 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 - 是否有任何快捷方式可以将对象、其中包含的对象(所有成员,无论可见性或类型)简化为关联数组,或将其类型转换为 @ 987654327@?我正在考虑Reflection的方向,但如果没有,我会想办法递归地执行它。
    • 反思将是漫长的道路。由于您在getJsonData() 函数中的类内部,您可以调用get_object_vars(),然后循环遍历该结果以查找更多对象。
    • 我几乎把它整理好了;现在的问题是递归。每个对象都有一个_parent 属性,因此可以遍历树到根。请参阅我的编辑以获取更新;也许我应该问另一个问题,因为这个问题现在已经从我的原始问题中抽象出来了。
    • 步行前简单的unset($array['_parent']); 应该可以解决问题。
    • 太棒了,感谢@Wrikken - 我开始尝试复杂的相等测试,将上下文对象$parent 作为用户数据传递给array_walk_recursive()。简单即美!此外,它的$array["\0class\0property"] 是因为我使用了强制转换导致的空字节污染。我想我会切换到get_object_vars()
    【解决方案3】:

    json_encode() 只会对公共成员变量进行编码。所以如果你想在你必须自己做的时候包含私人(正如其他人所建议的那样)

    【讨论】:

      【解决方案4】:

      在最简单的情况下,类型提示应该可以工作:

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

      【讨论】:

      • 如果您使用命名空间和自动加载器,这会给出冗长/丑陋的属性名称。
      • 这是最好的解决方案,精确而简洁!
      • 有没有办法获得更清晰的属性名称?
      • 为什么在道具名称的开头加上\u0000*\u0000?
      • 对私有属性没用。你们都应该了解en.wikipedia.org/wiki/Open/closed_principle
      【解决方案5】:

      我在同一个问题上花了几个小时。 我要转换的对象包含许多其他我不应该接触的定义(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;
      }
      

      【讨论】:

      • 还有另一个很好且准确的答案,已被接受。您的答案是否添加了一些完全不同、更高效或更紧凑的东西?我猜不是
      • 我会说实话;我认为这根本不能回答问题。
      • 大约6个月了;由于投票,我会定期返回这里,并为未来的访问者进行一些编辑;我仍然不知道这到底应该做什么。
      • unlink($thisAnswer);
      • inline php strings, eval, shell_exec(php)... c-c-combo.
      【解决方案6】:

      以下代码使用反射来完成这项工作。它假设您有要序列化的属性的 getter

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

      【讨论】:

      • 我现在很爱你!我会给你送些培根、啤酒或纸杯蛋糕,那纸杯蛋糕呢?
      • 这是一堂很棒的课!它也适用于受保护的对象项目。
      【解决方案7】:

      只需实现一个由 PHP JsonSerializable 提供的接口。

      【讨论】:

        【解决方案8】:

        我制作了一个很好的帮助类,它将带有 get 方法的对象转换为数组。 它不依赖于属性,只依赖于方法。

        所以我有一个包含两种方法的以下评论对象:

        评论

        • getAmountReviews : 整数
        • getReviews : cmets 数组

        评论

        • 获取主题
        • 获取描述

        我编写的脚本会将其转换为具有如下属性的数组:

            {
              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 Serializer which converts an object to an array that can be encoded to JSON.

        您所要做的就是将 json_encode 包裹在输出周围。

        关于脚本的一些信息:

        • 仅添加以 get 开头的方法
        • 私有方法被忽略
        • 构造函数被忽略
        • 方法名称中的大写字符将替换为下划线和小写字符

        【讨论】:

          【解决方案9】:

          我的版本:

          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

          【讨论】:

          • 正是我想要的。解决私人问题。简单而小巧。
          【解决方案10】:

          尝试使用这个,这对我来说很好。

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

          【讨论】:

            【解决方案11】:

            将变量类型从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)
               }
            }
            

            【讨论】:

            • 这很奇怪。但这是真的。
            猜你喜欢
            • 2012-05-16
            • 2010-10-08
            • 1970-01-01
            • 2016-12-22
            • 2018-07-20
            • 2021-12-16
            相关资源
            最近更新 更多