Symfony 8.2 New Published on

One name per API version, and groups to leave out

#[SerializedName('name', groups: 'v1')]: a property changes name depending on the group; ignored_groups leaves a group out without touching the class.

Topics covered: SerializerAPI

New in Symfony 8.2 (in development). Serialization groups choose which properties are output; they said nothing about what they are called. An API that gets a new version while keeping the old one therefore ended up with a second class, or with a loop that renames the keys by hand after the serializer.

One name per group

#[SerializedName] and #[SerializedPath] become repeatable and accept a groups argument:

#[Groups(['v1', 'v2'])]
#[SerializedName('product_name', groups: 'v1')]
public string $name;

Serialized with the v1 group, the property comes out as product_name; with v2, as name. A name declared without a group applies to all of them, and serves as a fallback. It also works the other way, on deserialization.

Leaving a group out

The new AbstractNormalizer::IGNORED_GROUPS ('ignored_groups') context removes the properties of a group, even if they also belong to a requested group:

$normalizer->normalize($books, 'json', [
    AbstractNormalizer::GROUPS => 'v2',
    AbstractNormalizer::IGNORED_GROUPS => 'internal',
]);

Unlike #[Ignore], which removes a property everywhere, it only applies where you pass it. With a context builder, it's withIgnoredGroups().

Your task

The bookshop serves its books in three places: the v1 API, which partners still read, the v2 API, and the back office. LivresController makes up by hand for what the groups can't express: it renames the v1 keys in a loop, and removes the stock from v2 with unset().

  1. In Livre, declare the v1 names (name, and a flat price_cents) for the v1 group only, without changing what v2 and the back office see.
  2. In LivresController, make v1 a plain serialization of the v1 group, and leave the internal group out of v2 through the context.

The three requests in the “Requests” tab must respond exactly as before.

Where the feature comes from: the original pull request.