Search code examples
symfonyapi-platform.com

Symfony serializer: removing fields after normalization


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.


Solution

  • 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);
                    }
                }
            }
        }
    }