Define Named Serializers with Different Settings in Symfony 8

Define Named Serializers with Different Settings in Symfony 8

Symfony Serializer is highly flexible and can be tuned to match various data formats and conventions. In real-world projects, a single serializer configuration is often not enough. For example, one external API may expect camel case fields, while another requires snake case. This tutorial explains how to define named serializers with different settings in a Symfony 8 application.

Symfony allows us to define multiple named serializer instances, each with its own configuration. These serializers can then be injected where needed, without affecting the default one.

Let's say we have the following entity class:

src/Entity/User.php

<?php

namespace App\Entity;

class User
{
    private string $firstName;

    private ?string $phone = null;

    public function getFirstName(): string { return $this->firstName; }
    public function setFirstName(string $firstName): void { $this->firstName = $firstName; }

    public function getPhone(): ?string { return $this->phone; }
    public function setPhone(?string $phone): void { $this->phone = $phone; }
}

By default, Symfony provides a single serializer service. To use additional configurations, we can define named serializers in the framework configuration. In this example, we define a named serializer that uses snake case fields (e.g., first_name) instead of camel case (e.g., firstName) and excludes fields with null values from the output.

config/packages/serializer.yaml

framework:
    serializer:
        named_serializers:
            snake_case:
                name_converter: 'serializer.name_converter.camel_case_to_snake_case'
                default_context:
                    skip_null_values: true

Symfony allows injecting named serializers using service aliases or the Target attribute.

src/Controller/TestController.php

<?php

namespace App\Controller;

use App\Entity\User;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\SerializerInterface;

class TestController
{
    public function __construct(
        private SerializerInterface $serializer, // default
        private SerializerInterface $snakeCaseSerializer, // named serializer
        #[Target('snakeCase.serializer')]
        private SerializerInterface $customSerializer, // explicitly targeted serializer
    ) {
    }

    #[Route('/')]
    public function index(): Response
    {
        $user = new User();
        $user->setFirstName('John');

        // {"firstName":"John","phone":null}
        $resp1 = $this->serializer->serialize($user, JsonEncoder::FORMAT);
        // {"first_name":"John"}
        $resp2 = $this->snakeCaseSerializer->serialize($user, JsonEncoder::FORMAT);
        // {"first_name":"John"}
        $resp3 = $this->customSerializer->serialize($user, JsonEncoder::FORMAT);

        return new Response($resp1.'<br>'.$resp2.'<br>'.$resp3);
    }
}

Named serializers are especially useful when:

  • Integrating with multiple third-party APIs that follow different conventions.
  • Producing different output formats from the same domain objects.
  • Keeping serialization logic clean and isolated instead of modifying context options everywhere.

Leave a Comment

Cancel reply

Your email address will not be published.