Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize DateTime in Symfony

Trying to use the serializer component in Symfony 3.3. I struggle with entities having 'DateTime' members.

My config.yml serializer init:

serializer:
    enable_annotations: true

Added this in service.yml:

datetime_method_normalizer:
    class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer
    public: false
    tags: [serializer.normalizer]

The deserialized code looks like this:

$yml = [...]   // It was created by serializer->serialize()
$serializer = $this->get('serializer');
$myObject = $serializer->deserialize($yml, MyObject::class, "yaml");

The error is get is: Expected argument of type "DateTime", "string" given in in vendor/symfony/symfony/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php (line 204)

I think the DateTimeNormalizer::denormalize never gets called. Any idea how to bring it back to life?

Info: DateTimeNormalizer::__constructor() is called.

like image 202
user3429660 Avatar asked Jun 26 '17 11:06

user3429660


2 Answers

As DateTime is a nested object, you should use PropertyInfo Component as described here — https://symfony.com/doc/current/components/serializer.html#recursive-denormalization-and-type-safety

The extraction of property information is performed by extractor classes.

https://symfony.com/doc/current/components/property_info.html#extractors

There are 4 types of extractors:

  • ReflectionExtractor
  • PhpDocExtractor
  • SerializerExtractor
  • DoctrineExtractor

For example, using ReflectionExtractor you need to specify type hint for either params or return type. It also looks for constructor params (requires to be enabled explicitly)

class Item {

   protected $date;

   public function setDate(\DateTime $date) {...}
   public function getDate() : \DateTime {...}
}

Property Info is registered automatically when option set:

# config/packages/framework.yaml 
framework:
  property_info: ~

After that you need to override serializer service to use it, or define a custom one. And the last part — add DateTimeNormalizer, so DateTime can be processed by serializer.

app.normalizer.item_normalizer:
    class: Symfony\Component\Serializer\Normalizer\ObjectNormalizer
    arguments:
      - null
      - null
      - null
      - '@property_info.reflection_extractor'
    tags: [ 'serializer.normalizer' ]

app.serializer.item:
    class: Symfony\Component\Serializer\Serializer
    public: true
    arguments:
      - [
          '@serializer.normalizer.datetime',
          '@app.normalizer.item_normalizer',
        ]
      - [ '@serializer.encoder.json' ]

That's it.

like image 94
Danila Pakulin Avatar answered Oct 26 '22 23:10

Danila Pakulin


This question break my brain recently, and I've two entities with dateTime property, the solution is custom denormalizer like this:

<?php

namespace MyBundle\Serializer\Normalizer;

use MyBundle\Entity\MyEntity1;
use MyBundle\Entity\MyEntity2;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

/**
 * DateTime hook normalizer
 */
class DateTimeHookNormalizer implements DenormalizerInterface
{
    /**
     * {@inheritdoc}
     */
    public function denormalize($data, $class, $format = null, array $context = array())
    {
        if (isset($data['MyDateTime1']) && is_string($data['MyDateTime1']))
        {
            $data['MyDateTime1'] = new \DateTime($data['MyDateTime1']);
        }
        if (isset($data['MyDateTime2']) && is_string($data['MyDateTime2']))
        {
            $data['MyDateTime2'] = new \DateTime($data['MyDateTime2']);
        }
        And more ...

        $normalizer = new ObjectNormalizer();//default normalizer

        return $normalizer->denormalize($data, $class, $format, $context);
    }
}

/**
 * {@inheritdoc}
 */
public function supportsDenormalization($data, $type, $format = null)
{
    return is_array($data) && ($type === MyEntity1::class || $type === MyEntity2::class);
}

And declare service like this :

# DateTime Hook Normalizer
Mybundle.normalizer.dateTimeHook:
    class: 'MybundleBundle\Serializer\Normalizer\DateTimeHookNormalizer'
    public: false
    tags: [serializer.normalizer]

It's ok for me, that work !

like image 24
pierre raby Avatar answered Oct 27 '22 00:10

pierre raby