Symfony serializer: removing fields after normalization

Viewed 375

After upgrading to Api platform 2.6, the non ApiResource entities started being serialized (ld-json) along with a fake identifier and their type. For example:

{
  "@type": "MetaData",
  "@id": "_:11294",
  "id": "bf1e417c-27ff-48c1-a591-40e5a43c708c",
  "key": "key1",
  "value": "value1"
}

we would like to remove these fields. To do so I've tried to implement a custom normalizer hoping to be able to hook in after the object is transformed in array and before the array is converted to a json.

Looking at the registered normalizers:

 ----------------------------------------------------------- ---------- --------------------------------------------------------------------------- 
  Service ID                                                  priority   Class name                                                                 
 ----------------------------------------------------------- ---------- --------------------------------------------------------------------------- 
  api_platform.serializer.normalizer.item                     -895       ApiPlatform\Core\Serializer\ItemNormalizer                                 
  api_platform.problem.normalizer.constraint_violation_list   -780       ApiPlatform\Core\Problem\Serializer\ConstraintViolationListNormalizer      
  api_platform.hydra.normalizer.collection_filters            -985       ApiPlatform\Core\Hydra\Serializer\CollectionFiltersNormalizer              
  api_platform.hydra.normalizer.error                         -800       ApiPlatform\Core\Hydra\Serializer\ErrorNormalizer                          
  api_platform.hydra.normalizer.entrypoint                    -800       ApiPlatform\Core\Hydra\Serializer\EntrypointNormalizer                     
  api_platform.hydra.normalizer.constraint_violation_list     -780       ApiPlatform\Core\Hydra\Serializer\ConstraintViolationListNormalizer        
  api_platform.hydra.normalizer.documentation                 -800       ApiPlatform\Core\Hydra\Serializer\DocumentationNormalizer                  
  api_platform.jsonld.normalizer.object                       -995       ApiPlatform\Core\JsonLd\Serializer\ObjectNormalizer                        
  api_platform.swagger.normalizer.api_gateway                 -780       ApiPlatform\Core\Swagger\Serializer\ApiGatewayNormalizer                   
  api_platform.openapi.normalizer.api_gateway                 -780       ApiPlatform\Core\Swagger\Serializer\ApiGatewayNormalizer                   
  api_platform.serializer.uuid_denormalizer                              ApiPlatform\Core\Bridge\RamseyUuid\Serializer\UuidDenormalizer             
  serializer.denormalizer.array                               -990       Symfony\Component\Serializer\Normalizer\ArrayDenormalizer                  
  serializer.normalizer.object                                -1000      Symfony\Component\Serializer\Normalizer\ObjectNormalizer                   
  serializer.normalizer.problem                               -890       Symfony\Component\Serializer\Normalizer\ProblemNormalizer                  
  serializer.normalizer.json_serializable                     -900       Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer         
  serializer.normalizer.datetime                              -910       Symfony\Component\Serializer\Normalizer\DateTimeNormalizer                 
  serializer.normalizer.data_uri                              -920       Symfony\Component\Serializer\Normalizer\DataUriNormalizer                  
  serializer.normalizer.dateinterval                          -915       Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer             
  serializer.normalizer.datetimezone                          -915       Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer             
  serializer.normalizer.constraint_violation_list             -915       Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer  
  api_platform.problem.normalizer.error                       -810       ApiPlatform\Core\Problem\Serializer\ErrorNormalizer                        
 ----------------------------------------------------------- ---------- --------------------------------------------------------------------------- 

the closest I've got was around the api_platform.jsonld.normalizer.object however if the priority I use is -994, I get the original object, if I use -996 I get the single string/boolean/numeric fields (so after the normalization).

Is there any way I can get the associative array so that I can remove @id and @type if @id starts with _:?

The line of the code that adds those properties seems to be this however that probably just adds to the context it doesn't include those fields in the normalized array.

1 Answers

No idea how to decorate the serializer, but I will handle your issue with an event subscriber.

The "POST_SERIALIZE" priority contains the serialized response body, and you can fetch it and modify it.

Like this:

<?php

namespace App\EventSubscriber;

use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class PostSerializeProductEventSubScriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::VIEW => ["removeFakeIdentifiersFromJsonLdResponseBody", EventPriorities::POST_SERIALIZE],
        ];
    }

    public function removeFakeIdentifiersFromJsonLdResponseBody(ViewEvent $event): void
    {
        $requestAcceptHeader = $event->getRequest()->headers->get('accept', 'none');
        if (str_contains($requestAcceptHeader, "application/ld+json")) {
            $this->removefakeIdentifiersFromResponseBody($event);
        }
    }

    private function removefakeIdentifiersFromResponseBody(ViewEvent $event): void
    {
        $responseBody = $event->getControllerResult();
        $decodedResponseBody = json_decode($responseBody);
        $this->removeFakeIdentifiersFromObject($decodedResponseBody);
        $encodedResponseBody = json_encode($decodedResponseBody);
        $event->setControllerResult($encodedResponseBody);
    }

    private function removeFakeIdentifiersFromObject(object $responseBody): void
    {
        foreach ($responseBody as $property => $value) {
            $this->removeFakeIdRecursively($property, $value, $responseBody);
        }
    }

    private function removeFakeIdRecursively(string $property, mixed $value, object $responseBody): void
    {
        if ($property === "@id" && str_starts_with($value, "_:")) {
            unset($responseBody->$property); // removes "@id"
            unset($responseBody->{"@type"});
        } elseif (is_object($value)) {
            $this->removeFakeIdentifiersFromObject($value);
        } elseif (is_array($value)) {
            foreach ($value as $object) {
                if (is_object($object)) {
                    $this->removeFakeIdentifiersFromObject($object);
                }
            }
        }
    }
}
Related