denormalizers are no longer aliased as normalizer in 4.4 · Issue #8550 · api-platform/core · GitHub
Skip to content

denormalizers are no longer aliased as normalizer in 4.4 #8550

Closed

Description

@jannes-io

API Platform version(s) affected: 4.4.0

Description

Commit d36c14c splits normalizer and denormalizer into dedicated ItemNormalizer and ItemDenormalizer. This causes overwritten decorators to no longer work (docs is also still outdated https://api-platform.com/docs/v4.4/core/serialization/#decorating-a-serializer-and-adding-extra-data-for-symfony)

How to reproduce

Create a serializer that decorates only 'api_platform.jsonld.normalizer.item', it no longer decorates during denormalize.

// small DTO to play with:
class Test
{
    public bool $test = false;
}


#[AsDecorator('api_platform.jsonld.normalizer.item')]
class ExampleSerializer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface
{
    public function __construct(
        #[AutowireDecorated]
        private readonly NormalizerInterface&DenormalizerInterface&SerializerAwareInterface $decorated,
    ) {
    }

    public function setSerializer(SerializerInterface $serializer): void
    {
        $this->decorated->setSerializer($serializer);
    }

    public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
    {
        $result = $this->decorated->denormalize($data, $type, $format, $context);
        $result->test = true;

        return $result;
    }

    public function supportsDenormalization(
        mixed $data,
        string $type,
        ?string $format = null,
        array $context = [],
    ): bool {
        return $this->decorated->supportsDenormalization($data, $type, $format, $context);
    }

    public function normalize(
        mixed $data,
        ?string $format = null,
        array $context = [],
    ): array|string|int|float|bool|ArrayObject|null {
        $result = $this->decorated->normalize($data, $format, $context);

       $result['extraProperty'] = true;
        return $result;
    }

    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
    {
        return $this->decorated->supportsNormalization($data, $format, $context);
    }

    public function getSupportedTypes(?string $format): array
    {
        return $this->decorated->getSupportedTypes($format);
    }
}

Given this setup, I expect normalize to add extraProperty and denormalize to set test to true. In 4.3 this worked, in 4.4 it no longer works and we have to add an extra decorator:

~ #[AsDecorator('api_platform.jsonld.normalizer.item')]
+ #[AsDecorator('api_platform.jsonld.denormalizer.item')]
~ class ExampleSerializer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface

Hence why this is a backwards compatibility break on a minor version (4.4).

Possible Solution
d36c14c should be removed from 4.4 and moved to 5.0

Additional Context

Activity

  1. jannes-io commented on Sep 21, 2026

    @jannes-io
    ContributorAuthor

    Current workaround:

    Downgrading all installed api-platform packages from ^4.3 to 4.3.* temporarily:

    -        "api-platform/doctrine-orm": "^4.3",
    -        "api-platform/elasticsearch": "^4.3",
    -        "api-platform/symfony": "^4.3",
    +        "api-platform/doctrine-common": "4.3.*",
    +        "api-platform/doctrine-orm": "4.3.*",
    +        "api-platform/documentation": "4.3.*",
    +        "api-platform/elasticsearch": "4.3.*",
    +        "api-platform/http-cache": "4.3.*",
    +        "api-platform/hydra": "4.3.*",
    +        "api-platform/json-schema": "4.3.*",
    +        "api-platform/jsonld": "4.3.*",
    +        "api-platform/metadata": "4.3.*",
    +        "api-platform/openapi": "4.3.*",
    +        "api-platform/serializer": "4.3.*",
    +        "api-platform/state": "4.3.*",
    +        "api-platform/symfony": "4.3.*",
    +        "api-platform/validator": "4.3.*",

    (note: your installed api-platform packages may vary, this is for a orm + elasticsearch + symfony project)

  2. Amoifr commented on Sep 22, 2026

    @Amoifr
    Contributor

    Confirmed, and it reaches further than JSON-LD.

    The split gave every format a denormalizer whose priority sits one above its normalizer, so the serializer reaches it first and the decorated service is never asked to denormalize:

    format normalizer denormalizer (4.4)
    jsonld -890 -889
    jsonapi -890 -889
    serializer, the one the documentation uses most -895 -894

    What makes it quiet is that ItemNormalizer still implements denormalize() and supportsDenormalization() in 4.4, so a decorator of the old service is neither wrong nor broken, just shadowed. Nothing fails, the data simply comes back undecorated.

    ItemDenormalizer::supportsDenormalization() only adds the format check on top of the parent, so it answers for exactly what the old service used to answer for.

    Happy to send the documentation update once you have picked a direction, since the page still tells people to decorate the normalizer for both. The direction itself is yours: swapping the two priorities restores the old behaviour but makes the new class unreachable whenever the normalizer also supports the type, so the alternative is to keep it and say in UPGRADE that a decorator now has to cover both services.

  3. jannes-io commented on Sep 22, 2026

    @jannes-io
    ContributorAuthor

    This needs @soyuka or a maintainer’s decision.

  4. added theissue type on Sep 26, 2026
  5. soyuka commented on Sep 29, 2026

    @soyuka
    Member

    The split in #7713 is intentional: since 4.4, denormalization goes through a dedicated ItemDenormalizer service (api_platform.jsonld.denormalizer.item, and the matching ids for JSON:API and the generic serializer), which runs before the normalizer. We do not plan to swap the priorities back, because that would make the new denormalizers unreachable.

    To change denormalization, decorate the denormalizer service. To change both directions, decorate both services:

    #[AsDecorator('api_platform.jsonld.normalizer.item')]
    final class ApiNormalizer implements NormalizerInterface { /* ... */ }
    
    #[AsDecorator('api_platform.jsonld.denormalizer.item')]
    final class ApiDenormalizer implements DenormalizerInterface { /* ... */ }

    You are right that the serialization docs still show one decorator for both directions. That page needs an update for 4.4, and a docs PR is welcome (@Amoifr offered one). Closing, because the behaviour is by design.

  6. jannes-io commented on Sep 29, 2026

    @jannes-io
    ContributorAuthor

    But @soyuka this is a pretty big backwards compatibility break. Decorating the normalizer is per-documentation the way users used to add custom denormalization logic. This is now suddenly broken on a minor version update and thus does not belong in 4.4. It should be pushed to 5.0.

    Why was this part of 4.4?

  7. soyuka commented on Sep 29, 2026

    @soyuka
    Member

    Reopening: my earlier close was wrong. The documentation told users to decorate the item normalizer for both directions, and that stopped working in 4.4 without any warning. That is a BC break in a minor version.

    #8606 adds a BC layer for 4.4. When a service decorates api_platform.*.normalizer.item and implements DenormalizerInterface, denormalization goes through that decorator again, as in 4.3, and a deprecation asks you to decorate api_platform.*.denormalizer.item instead. The layer stays until 6.0.

  8. reopened this on Sep 29, 2026
  9. added a commit that references this issue on Sep 29, 2026
    e727149
  10. jannes-io commented on Oct 2, 2026

    @jannes-io
    ContributorAuthor

    @soyuka is there a timing on the release for this fix?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions