【问题标题】:Denormalize nested structure in objects with Symfony 2 serializer使用 Symfony 2 序列化器对对象中的嵌套结构进行非规范化
【发布时间】:2017-02-23 07:54:01
【问题描述】:

我正在开发一个版本为 2.8 的 Symfony 2 项目,我正在使用内置组件 Serializer -> http://symfony.com/doc/current/components/serializer.html

我有一个由 Web 服务提供的 JSON 结构。 反序列化后,我想对对象中的内容进行非规范化。这是我的结构(汽车应用程序上下文中的模型/制造)。

[{
"0": {
    "id": 0,
    "code": 1,
    "model": "modelA",
    "make": {
        "id": 0,
        "code": 1,
        "name": "makeA"
    }
  }
} , {
 "1": {
    "id": 1,
    "code": 2,
    "model": "modelB",
    "make": {
        "id": 0,
        "code": 1,
        "name": "makeA"
    }
  }
}]

我的想法是填充一个 VehicleModel 对象,其中包含对 VehicleMake 对象的引用。

class VehicleModel {
    public $id;
    public $code;
    public $model;
    public $make; // VehicleMake
}

这是我的工作:

// Retrieve data in JSON
$data = ...
$serializer = new Serializer([new ObjectNormalizer(), new ArrayDenormalizer()], [new JsonEncoder()]);
$models = $serializer->deserialize($data, '\Namespace\VehicleModel[]', 'json');

结果,我的对象VehicleModel 被正确填充,但$make 在逻辑上是一个键/值数组。这里我想要一个VehicleMake

有没有办法做到这一点?

【问题讨论】:

    标签: php json symfony denormalization


    【解决方案1】:

    在 Symfony4+ 中,您可以注入序列化程序,它会根据您的 phpdoc(例如 @var)或类型提示为您完成工作。 Phpdoc 似乎更安全,因为它管理对象的集合。

    例子:

    App\Model\Skill.php

    <?php
    
    namespace App\Model;
    
    class Skill
    {
        public $name = 'Taxi Driver';
    
        /** @var Category */
        public $category;
    
        /** @var Person[] */
        public $people = [];
    }
    

    App\Model\Category.php

    <?php
    
    namespace App\Model;
    
    class Category
    {
        public $label = 'Transports';
    }
    

    App\Model\Person.php

    <?php
    
    namespace App\Model;
    
    class Person
    {
        public $firstname;
    }
    

    App\Command\TestCommand.php

    <?php
    
    namespace App\Command;
    
    use App\Model\Category;
    use App\Model\Person;
    use App\Model\Skill;
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Output\OutputInterface;
    use Symfony\Component\Serializer\SerializerInterface;
    
    class TestCommand extends Command
    {
        /**
         * @var SerializerInterface
         */
        private $serializer;
    
        public function __construct(SerializerInterface $serializer)
        {
            parent::__construct();
    
            $this->serializer = $serializer;
        }
    
        protected function configure()
        {
            parent::configure();
    
            $this
                ->setName('test')
                ->setDescription('Does stuff');
        }
    
        protected function execute(InputInterface $input, OutputInterface $output)
        {
            $personA            = new Person();
            $personA->firstname = 'bruno';
            $personB            = new Person();
            $personB->firstname = 'alice';
    
            $badge           = new Skill();
            $badge->name     = 'foo';
            $badge->category = new Category();
            $badge->people   = [$personA, $personB];
    
            $output->writeln(
                $serialized = $this->serializer->serialize($badge, 'json')
            );
    
            $test = $this->serializer->deserialize($serialized, Skill::class, 'json');
    
            dump($test);
    
            return 0;
        }
    }
    

    将给出以下预期结果:

    {"name":"foo","category":{"label":"Transports"},"people":[{"firstname":"bruno"},{"firstname":"alice"}]}
    
    ^ App\Model\BadgeFacade^ {#2531
      +name: "foo"
      +category: App\Model\CategoryFacade^ {#2540
        +label: "Transports"
      }
      +people: array:2 [
        0 => App\Model\PersonFacade^ {#2644
          +firstname: "bruno"
        }
        1 => App\Model\PersonFacade^ {#2623
          +firstname: "alice"
        }
      ]
    }
    

    【讨论】:

      【解决方案2】:

      如果您的 Vehicle 类有一些类型提示,最简单的方法是使用 ReflectionExtractor

      class VehicleModel {
          public $id;
          public $code;
          public $model;
          /** @var VehicleMake */
          public $make;
      }
      

      您可以在初始化Serializer 时将Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor 作为参数传递给ObjectNormalizer

      $serializer = new Serializer([new ObjectNormalizer(null, null, null, new ReflectionExtractor()), new ArrayDenormalizer()], [new JsonEncoder()]);
      $models = $serializer->deserialize($data, '\Namespace\VehicleModel[]', 'json');
      

      【讨论】:

        【解决方案3】:

        如果您在非规范化方面需要更大的灵活性,最好创建自己的非规范化器。

        $serializer = new Serializer(
          [
            new ArrayNormalizer(), 
            new VehicleDenormalizer(), 
            new VehicleMakeDenormalizer()
          ], [
            new JsonEncoder()
          ]
        );
        $models = $serializer->deserialize(
          $data, 
          '\Namespace\VehicleModel[]', 
          'json'
        );
        

        这里是这种反规范化器的粗略代码

        class VehicleDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
            {
              public function denormalize($data, $class, $format, $context) 
              {
                $vehicle = new VehicleModel();
                ...
                $vehicleMake = $this->denormalizer->denormalize(
                  $data->make,
                  VehicleMake::class,
                  $format,
                  $context
                );
                $vehicle->setMake($vehicleMake);
                ...
              }
            }
        

        我只是怀疑我们应该依赖$this-&gt;denormalizer-&gt;denormalize(因为我们使用Symfony\Component\Serializer\Serializer,它才能正常工作)还是我们必须明确地将VehicleMakeDenormalizer注入VehicleDenormalizer

        $vehicleDenormalizer = new VehicleDenormalizer();
        $vehicleDenormalizer->setVehicleMakeDenormalizer(new VehicleMakeDenormalizer());
        

        【讨论】:

          【解决方案4】:

          ObjectNormalizer 需要更多配置。您至少需要提供PropertyTypeExtractorInterface 类型的第四个参数。

          这是一个(相当老套的)示例:

          <?php
          use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
          use Symfony\Component\PropertyInfo\Type;
          use Symfony\Component\Serializer\Encoder\JsonEncoder;
          use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
          use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
          use Symfony\Component\Serializer\Serializer;
          
          $a = new VehicleModel();
          $a->id = 0;
          $a->code = 1;
          $a->model = 'modalA';
          $a->make = new VehicleMake();
          $a->make->id = 0;
          $a->make->code = 1;
          $a->make->name = 'makeA';
          
          $b = new VehicleModel();
          $b->id = 1;
          $b->code = 2;
          $b->model = 'modelB';
          $b->make = new VehicleMake();
          $b->make->id = 0;
          $b->make->code = 1;
          $b->make->name = 'makeA';
          
          $data = [$a, $b];
          
          $serializer = new Serializer(
              [new ObjectNormalizer(null, null, null, new class implements PropertyTypeExtractorInterface {
                  /**
                   * {@inheritdoc}
                   */
                  public function getTypes($class, $property, array $context = array())
                  {
                      if (!is_a($class, VehicleModel::class, true)) {
                          return null;
                      }
          
                      if ('make' !== $property) {
                          return null;
                      }
          
                      return [
                          new Type(Type::BUILTIN_TYPE_OBJECT, true, VehicleMake::class)
                      ];
                  }
              }), new ArrayDenormalizer()],
              [new JsonEncoder()]
          );
          
          $json = $serializer->serialize($data, 'json');
          print_r($json);
          
          $models = $serializer->deserialize($json, VehicleModel::class . '[]', 'json');
          print_r($models);
          

          请注意,在您的示例 json 中,第一个条目有一个数组作为 make 的值。我认为这是一个错字,如果是故意的,请发表评论。

          为了使这个更加自动化,您可能需要尝试PhpDocExtractor

          【讨论】:

          • 你说得对,我的 json 中有错字。我更新了我的问题。
          • ObjectNormaliser 在构造函数中只需要三个参数,第三个实现 PropertyAccessorInterface,对吧?
          • 哦,我只在sf3上测试过这个。所以api可能发生了变化。如果在 v2.8 中无法添加类型提取器,那么此答案可能不适合您。
          • 好吧,它只在主要版本 3.0 中可用
          • 我正在使用 symfony 2.8,我在这里遇到了同样的问题。我在开发时使用 symfony 3.2 制作了一个外部包,当我将包导入 symfony 2.8 项目时,反序列化不是递归的。该功能仅适用于 symfony >3.1 版本 [github.com/symfony/symfony/blob/3.1/src/Symfony/Component/… symfony 3.1 上的源代码)[symfony.com/doc/current/components/… 递归非规范化文档)
          猜你喜欢
          • 2017-05-02
          • 1970-01-01
          • 2020-05-06
          • 2017-01-27
          • 2018-09-21
          • 2022-10-25
          • 2021-03-19
          • 2019-07-26
          • 2012-01-28
          相关资源
          最近更新 更多