【问题标题】:Deserializing from JSON into PHP, with casting?从 JSON 反序列化到 PHP,使用强制转换?
【发布时间】:2010-10-14 02:16:40
【问题描述】:

假设我有一个具有“名称”和“密码”属性的用户类,以及一个“保存”方法。当通过 json_encode 将此类的对象序列化为 JSON 时,该方法被正确跳过,我最终得到类似 {'name': 'testName', 'password': 'testPassword'} 的内容。

但是,当通过 json_decode 反序列化时,我最终得到了一个 StdClass 对象而不是一个 User 对象,这是有道理的,但这意味着该对象缺少“保存”方法。有什么方法可以将生成的对象转换为用户,或者为 json_decode 提供一些提示,以了解我期待什么类型的对象?

【问题讨论】:

    标签: php json serialization


    【解决方案1】:

    老问题,新答案。

    创建自己的界面来匹配JsonSerializable 怎么样? ;)

    /**
     * Interface JsonUnseriablizable
     */
    interface JsonUnseriablizable {
        /**
         * @param array $json
         */
        public function jsonUnserialize(array $json);
    }
    

    示例:

    /**
     * Class Person
     */
    class Person implements JsonUnseriablizable {
    
        public $name;
        public $dateOfBirth;
    
        /**
         * @param string $json
         */
        public function jsonUnserialize(array $json)
        {
            $this->name = $json['name'] ?? $this->name;
            $this->dateOfBirth = $json['date_of_birth'] ?? $this->dateOfBirth;
        }
    }
    
    $json = '{"name":"Bob","date_of_birth":"1970-01-01"}';
    
    $person = new Person();
    if($person instanceof JsonUnseriablizable){
        $person->jsonUnserialize(json_decode($json, true, 512, JSON_THROW_ON_ERROR));
    }
    
    var_dump($person->name);
    

    【讨论】:

      【解决方案2】:

      下面是使用静态(即您知道代码中的类类型)和动态(即您只知道运行时的类类型)将 JSON 反序列化回 PHP 对象的示例:

      代码

      <?php
      
      class Car
      {
          private $brand;
          private $model;
          private $year;
      
          public function __construct($brand, $model, $year)
          {
              $this->brand = $brand;
              $this->model = $model;
              $this->year = $year;
          }
      
          public function toJson()
          {
              $arr = array(
                  'brand' => $this->brand,
                  'model' => $this->model,
                  'year' => $this->year,
              );
      
              return json_encode($arr);
          }
      
          public static function fromJson($json)
          {
              $arr = json_decode($json, true);
      
              return new self(
                  $arr['brand'],
                  $arr['model'],
                  $arr['year']
              );
          }
      }
      
      // original object
      echo 'car1: ';
      $car1 = new Car('Hyundai', 'Tucson', 2010);
      var_dump($car1);
      
      // serialize
      echo 'car1class: ';
      $car1class = get_class($car1); // need the class name for the dynamic case below. this would need to be bundled with the JSON to know what kind of class to recreate.
      var_dump($car1class);
      
      echo 'car1json: ';
      $car1Json = $car1->toJson();
      var_dump($car1Json);
      
      // static recreation with direct invocation. can only do this if you know the class name in code.
      echo 'car2: ';
      $car2 = Car::fromJson($car1Json);
      var_dump($car2);
      
      // dynamic recreation with reflection. can do this when you only know the class name at runtime as a string.
      echo 'car3: ';
      $car3 = (new ReflectionMethod($car1class, 'fromJson'))->invoke(null, $car1Json);
      var_dump($car3);
      

      输出

      car1: object(Car)#1 (3) {
        ["brand":"Car":private]=>
        string(7) "Hyundai"
        ["model":"Car":private]=>
        string(6) "Tucson"
        ["year":"Car":private]=>
        int(2010)
      }
      car1class: string(3) "Car"
      car1json: string(48) "{"brand":"Hyundai","model":"Tucson","year":2010}"
      car2: object(Car)#2 (3) {
        ["brand":"Car":private]=>
        string(7) "Hyundai"
        ["model":"Car":private]=>
        string(6) "Tucson"
        ["year":"Car":private]=>
        int(2010)
      }
      car3: object(Car)#4 (3) {
        ["brand":"Car":private]=>
        string(7) "Hyundai"
        ["model":"Car":private]=>
        string(6) "Tucson"
        ["year":"Car":private]=>
        int(2010)
      }
      

      【讨论】:

        【解决方案3】:

        也许hydration 模式会有所帮助。

        基本上,您实例化一个新的空对象 (new User()),然后使用来自 StdClass 对象的值填充属性。例如,您可以在 User 中有一个 hydrate 方法。

        如果可能,您可以使User constructor 接受StdClass 类型的可选参数并在实例化时获取值。

        【讨论】:

          【解决方案4】:

          有点晚了,但另一种选择是使用 symfony 序列化程序来反序列化 xml、json 以及任何对象。

          这里是文档: http://symfony.com/doc/current/components/serializer.html#deserializing-in-an-existing-object

          【讨论】:

          • 所有答案中唯一完全可重复使用的解决方案,并且非常易于应用。应该是公认的答案!
          【解决方案5】:

          老问题,但也许有人会觉得这很有用。
          我创建了一个带有静态函数的抽象类,您可以在对象上继承该抽象类,以便将任何 JSON 反序列化为继承类实例。

          abstract class JsonDeserializer
          {
              /**
               * @param string|array $json
               * @return $this
               */
              public static function Deserialize($json)
              {
                  $className = get_called_class();
                  $classInstance = new $className();
                  if (is_string($json))
                      $json = json_decode($json);
          
                  foreach ($json as $key => $value) {
                      if (!property_exists($classInstance, $key)) continue;
          
                      $classInstance->{$key} = $value;
                  }
          
                  return $classInstance;
              }
              /**
               * @param string $json
               * @return $this[]
               */
              public static function DeserializeArray($json)
              {
                  $json = json_decode($json);
                  $items = [];
                  foreach ($json as $item)
                      $items[] = self::Deserialize($item);
                  return $items;
              }
          }
          

          您可以通过在具有 JSON 将具有的值的类上继承它来使用它:

          class MyObject extends JsonDeserializer
          {
              /** @var string */
              public $property1;
          
              /** @var string */
              public $property2;
          
              /** @var string */
              public $property3;
          
              /** @var array */
              public $array1;
          }
          

          示例用法:

          $objectInstance = new MyObject();
          $objectInstance->property1 = 'Value 1';
          $objectInstance->property2 = 'Value 2';
          $objectInstance->property3 = 'Value 3';
          $objectInstance->array1 = ['Key 1' => 'Value 1', 'Key 2' => 'Value 2'];
          
          $jsonSerialized = json_encode($objectInstance);
          
          $deserializedInstance = MyObject::Deserialize($jsonSerialized);
          

          如果您的 JSON 包含目标对象的数组,您可以使用 ::DeserializeArray 方法。

          Here 是一个可运行的示例。

          【讨论】:

          • 优秀。我采用了您的 Deserialize() 方法的一部分,并在我的构造函数中使用了它,该构造函数接收 JSON 作为参数。通过这种方式,我可以创建新类,将其传递到 JSON 中,结果是我的类的一个实例,其中填充了数据。谢谢。
          【解决方案6】:

          看看我写的这个类:

          https://github.com/mindplay-dk/jsonfreeze/blob/master/mindplay/jsonfreeze/JsonSerializer.php

          它保留了一个名为“#type”的 JSON 对象属性来存储类名,它有一些限制,如下所述:

          Serialize/unserialize PHP object-graph to JSON

          【讨论】:

            【解决方案7】:

            我必须说我有点失望,这不仅仅是标准的、现成的功能——某些库的功能,如果不是 JSON 本身的话。为什么您希望两边都有本质上相似的对象? (无论如何,JSON 会允许你)

            我在这里遗漏了什么吗?有没有这样做的图书馆? (AFAICT,[thrift,protocol buffers,avro] 实际上都没有用于 javascript 的 API。对于我的问题,我对 JS PHP 最感兴趣,对 JS python 也很感兴趣。)

            【讨论】:

            • 纯数据格式,例如 JSON 或 XML,只处理数据的表示,而不处理数据的解释或含义 -您可以使用这些(或任何数量的其他格式)来序列化对象(或任何数量的其他东西),但序列化本身超出了通用数据格式的范围。
            【解决方案8】:

            要回答您的直接问题,不,没有办法使用 json_encode/json_decode 来执行此操作。 JSON 被设计并指定为一种用于编码信息的格式,而不是用于序列化对象的格式。 PHP 功能并没有超出这个范围。

            如果您对从 JSON 重新创建对象感兴趣,一种可能的解决方案是对层次结构中的所有对象使用静态方法,该方法接受 stdClass/string 并填充看起来像这样的变量

            //semi pseudo code, not tested
            static public function createFromJson($json){
                //if you pass in a string, decode it to an object
                $json = is_string($json) ? json_decode($json) : $json;
            
                foreach($json as $key=>$value){
                    $object = new self();
                    if(is_object($value)){
                        $object->{$key} = parent::createFromJson($json);
                    }
                    else{
                        $object->{$key} = $value;
                    }
                }
            
                return $object;
            }
            

            我没有对此进行测试,但我希望它能传达这个想法。理想情况下,您的所有对象都应该从某个基础对象(通常称为“类对象”)扩展而来,因此您只能在一个地方添加此代码。

            【讨论】:

              【解决方案9】:

              您可以创建某种类型的 FactoryClass:

              function create(array $data)  
              {
                  $user = new User();
                  foreach($data as $k => $v) {
                      $user->$k = $v;
                  }
                  return $user;
              }
              

              这不是您想要的解决方案,但它可以完成您的工作。

              【讨论】:

                【解决方案10】:

                我认为处理这个问题的最好方法是通过构造函数,直接或通过工厂:

                class User
                {
                   public $username;
                   public $nestedObj; //another class that has a constructor for handling json
                   ...
                
                   // This could be make private if the factories below are used exclusively
                   // and then make more sane constructors like:
                   //     __construct($username, $password)
                   public function __construct($mixed)
                   {
                       if (is_object($mixed)) {
                           if (isset($mixed->username))
                               $this->username = $mixed->username;
                           if (isset($mixed->nestedObj) && is_object($mixed->nestedObj))
                               $this->nestedObj = new NestedObject($mixed->nestedObj);
                           ...
                       } else if (is_array($mixed)) {
                           if (isset($mixed['username']))
                               $this->username = $mixed['username'];
                           if (isset($mixed['nestedObj']) && is_array($mixed['nestedObj']))
                               $this->nestedObj = new NestedObj($mixed['nestedObj']);
                           ...
                       }
                   }
                   ...
                
                   public static fromJSON_by_obj($json)
                   {
                       return new self(json_decode($json));
                   }
                
                   public static fromJSON_by_ary($json)
                   {
                       return new self(json_decode($json, TRUE)); 
                   }
                }
                

                【讨论】:

                • 没错。 JSON 无法编码原始对象的类型。
                【解决方案11】:

                我知道 JSON 不支持函数的序列化,这是完全可以接受的,甚至是期望的。我的类目前被用作与 JavaScript 通信的值对象,而函数将毫无意义(并且常规的序列化函数不可用)。

                但是,随着与这些类相关的功能的增加,将它们的实用函数(例如本例中的用户的 save())封装在实际类中对我来说是有意义的。这确实意味着它们不再是严格意义上的值对象,这就是我遇到上述问题的地方。

                我的一个想法是在 JSON 字符串中指定类名,结果可能是这样的:

                $string = '{"name": "testUser", "password": "testPassword", "class": "User"}';
                $object = json_decode ($string);
                $user = ($user->class) $object;
                

                相反的情况是在 json_encode 期间/之后设置类属性。我知道,有点令人费解,但我只是想将相关代码放在一起。我可能最终会再次将我的实用程序函数从类中移除;修改后的构造函数方法似乎有点不透明,并且在嵌套对象时遇到了麻烦。

                不过,我非常感谢这一点以及任何未来的反馈。

                【讨论】:

                • 看看 J2EE 世界,您将创建一个单独的类来封装您将对纯数据对象执行的操作。在这种情况下,可能是 User 和 UserHandler。
                【解决方案12】:

                简答:不(我不知道*)

                长答案:json_encode 只会序列化公共变量。正如JSON spec 所见,没有“函数”数据类型。这些都是您的方法未序列化到 JSON 对象中的两个原因。

                Ryan Graham 是对的 - 将这些对象重新创建为非 stdClass 实例的唯一方法是在反序列化后重新创建它们。

                例子

                <?php
                
                class Person
                {
                    public $firstName;
                    public $lastName;
                
                    public function __construct( $firstName, $lastName )
                    {
                        $this->firstName = $firstName;
                        $this->lastName = $lastName;
                    }
                
                    public static function createFromJson( $jsonString )
                    {
                        $object = json_decode( $jsonString );
                        return new self( $object->firstName, $object->lastName );
                    }
                
                    public function getName()
                    {
                        return $this->firstName . ' ' . $this->lastName;
                    }
                }
                
                $p = new Person( 'Peter', 'Bailey' );
                $jsonPerson = json_encode( $p );
                
                $reconstructedPerson = Person::createFromJson( $jsonPerson );
                
                echo $reconstructedPerson->getName();
                

                或者,除非您确实需要 JSON 格式的数据,否则您可以使用 normal serialization 并利用 __sleep() and __wakeup() hooks 来实现额外的自定义。

                *a previous question of my own 中,有人建议您可以实现一些 SPL 接口来自定义 json_encode() 的输入/输出,但我的测试表明这些都是徒劳的。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-03-24
                  • 2018-02-20
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-10-26
                  相关资源
                  最近更新 更多