Deserialize DateTime in Symfony
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.
This question break my brain recently, and I've two entities with dateTime property, the solution is custom denormalizer like this:
<?phpnamespace 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 NormalizerMybundle.normalizer.dateTimeHook: class: 'MybundleBundle\Serializer\Normalizer\DateTimeHookNormalizer' public: false tags: [serializer.normalizer]
It's ok for me, that work !
The only official way is seems to declare a callback
:
$callback = function ($dateTime) {return $dateTime instanceof \DateTime ? $dateTime->format(\DateTime::ISO8601) : '';};$normalizer->setCallbacks(array('createdAt' => $callback));$serializer = new Serializer(array($normalizer), array($encoder));