Using the JSON serializer makes the messages a lot more readable. But there are still instances where this is not enough or might even be a step back from the default serializer. For example when using UUIDs. When you want to have this, you need to define a custom serializer.

I've setup a custom serializer anyway to deserialize DTOs from JSON and to return a json serialized response. This is configured in my CustomSerializer class.

<?php

declare(strict_types=1);

namespace App\Service\Serializer;

use Doctrine\Common\Annotations\AnnotationReader;
use GBProd\UuidNormalizer\UuidDenormalizer;
use GBProd\UuidNormalizer\UuidNormalizer;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
use Symfony\Component\Serializer\Serializer;

final class CustomSerializer
{
    public Serializer $serializer;

    public function __construct()
    {
        $classMetadataFactory = new ClassMetadataFactory(
            new AnnotationLoader(
                new AnnotationReader()
            )
        );

        $this->serializer = new Serializer(
            [
                new ArrayDenormalizer(),
                new DateTimeNormalizer(),
                new DataUriNormalizer(),
                new UuidNormalizer(),
                new UuidDenormalizer(),
                new ObjectNormalizer(
                    $classMetadataFactory,
                    null,
                    null,
                    new PhpDocExtractor()
                ),
                new PropertyNormalizer(),
            ],
            [
                new JsonEncoder(),
            ]
        );
    }

    public function serialize($data): string
    {
        return $this->serializer->serialize($data, JsonEncoder::FORMAT, [
            ObjectNormalizer::SKIP_NULL_VALUES => true,
            ObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
        ]);
    }

    public function deserialize(string $data, string $type)
    {
        return $this->serializer->deserialize($data, $type, JsonEncoder::FORMAT);
    }
}
CustomSerializer.php

Whenever I need a serializer I use it through the CustomSerializer.

You can setup the serializer wherever you want. The nice advantage here is that I can make sure, that everything within the app is serialized the same way.

Now to the Messenger. The only thing that we need to do is to supply this serializer to the messenger. Therefore we will build a custom transport class like the following:

<?php

declare(strict_types=1);

namespace App\Messenger\Transport;

use App\Service\Serializer\CustomSerializer;
use Symfony\Component\Messenger\Transport\Serialization\Serializer;

final class CustomTransportSerializer extends Serializer
{
    public function __construct(CustomSerializer $customSerializer)
    {
        parent::__construct($customSerializer->serializer);
    }
}

We simply extend the JSON serializer from Symfony and inject our serializer instead of the default Symfony one. Then we only need to configure it in our messenger.yaml.

framework:
  messenger:
    transports:
      async:
        serializer: App\Messenger\Transport\CustomTransportSerializer

And that's it. This way a UUIDInterface in an PHP object will be serialized to JSON as a string and will be deserialized back to a UUIDInterface.