D
D
Daria Motorina2020-08-11 21:30:48
symfony
Daria Motorina, 2020-08-11 21:30:48

How to work with custom serialization/normalization?

Based on my previous question, I want to clarify how it is correct.

There is a NearEarthObject entity and a PaginatedCollection pagination class that stores an array of these entities.
If I created a normalizer for PaginatedCollection and called serialization in the controller like

$serializer = new Serializer([new PaginatedCollectionNormalizer], [new JsonEncoder]);
$serializer->serialize($paginatedCollection);

Then the NearEarthObject list was not serialized, i.e. it just outputs an empty array. If I wrote another Normalizer, eg. PropertyNormalizer or ObjectNormalizer, then a bunch of errors "it is impossible to normalize an object" rained down.
I had to force NearEarthObject and PaginatedCollection to implement the JsonSerializable interface in order to get an adequate final serialized object.

Code:
App\Entity\NearEarthObject.php
App\Pagination\PaginatedCollection.php
App\Pagination\PaginatedFactory.php
NearEarthObjectController.php
PaginatedCollectionNormalizer.php
Response Structure

{
    "items": [
        {
            "id": 39,
            "name": "523707 (2014 JM25)",
            "reference": 2523707,
            "speed": 11950.225447854,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-08 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 49,
            "name": "9162 Kwiila (1987 OA)",
            "reference": 2009162,
            "speed": 81164.553763328,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-07 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 53,
            "name": "468730 (2010 MN51)",
            "reference": 2468730,
            "speed": 58929.965618653,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-06 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 54,
            "name": "510073 (2010 JF88)",
            "reference": 2510073,
            "speed": 62117.643247045,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-06 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 68,
            "name": "494713 (2005 OU2)",
            "reference": 2494713,
            "speed": 78875.305112096,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-05 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 70,
            "name": "228502 (2001 TE2)",
            "reference": 2228502,
            "speed": 64971.943374331,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-05 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 71,
            "name": "36284 (2000 DM8)",
            "reference": 2036284,
            "speed": 109370.58384093,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-04 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 72,
            "name": "(2002 CB19)",
            "reference": 3114030,
            "speed": 32990.860071831,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-04 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 74,
            "name": "68347 (2001 KB67)",
            "reference": 2068347,
            "speed": 34199.451888508,
            "is_hazardous": true,
            "date": {
                "date": "2020-08-04 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 82,
            "name": "512245 (2016 AU8)",
            "reference": 2512245,
            "speed": 32235.678141793,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-03 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        }
    ],
    "total": 66,
    "count": 10,
    "links": {
        "self": "/neo/hazardous?page=1",
        "first": "/neo/hazardous?page=1",
        "last": "/neo/hazardous?page=7",
        "next": "/neo/hazardous?page=2"
    }
}



Questions : how to do it right - entities should be serialized by default, why, when they get into the wrapper class, serialization stops working? Is a normalizer needed in this chain if it does nothing?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
D
Daria Motorina, 2020-08-12
@glaphire

Many thanks to BoShurik for commenting on the package symfony/property-info
Solution for non-serializable nested elements inside an object:
Install all packages

symfony/serializer
symfony/property-access
symfony/property-info
phpdocumentor/reflection-docblock

Inside the class, where there is a property - an array of objects, write an annotation
<?php

namespace App\Pagination;

use App\Entity\NearEarthObject;

class PaginatedCollection
{
    /**
     * @param NearEarthObject[] $items
     */
    private $items;
    ...

Inside the method where serialization takes place, write:
$extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
        $converter = new CamelCaseToSnakeCaseNameConverter();
        $normalizers = [
            new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d']),
            new ArrayDenormalizer(),
            new ObjectNormalizer(null, $converter, null, $extractor),
        ];
        $encoders = [new JsonEncoder()];

        $serializer = new Serializer($normalizers, $encoders);
        $serializedPaginatedCollection = $serializer->serialize($paginatedCollection, 'json');

Final controller
<?php

namespace App\Controller;

use App\Entity\NearEarthObject;
use App\Pagination\PaginationFactory;
use App\Repository\NearEarthObjectRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

class NearEarthObjectController extends AbstractController
{
    private EntityManagerInterface $entityManager;
    /**
     * @var PaginationFactory
     */
    private $paginationFactory;

    public function __construct(EntityManagerInterface $entityManager, PaginationFactory $paginationFactory)
    {
        $this->entityManager = $entityManager;
        $this->paginationFactory = $paginationFactory;
    }

    /**
     * @Route("/neo/hazardous", name="neo_hazardous", methods={"GET"})
     */
    public function hazardousAction(Request $request, ObjectNormalizer $objectNormalizer)
    {
        /**
         * @var NearEarthObjectRepository $nearEarthObjectRepository
         */
        $nearEarthObjectRepository = $this
            ->entityManager
            ->getRepository(NearEarthObject::class);

        //TODO: add getting is_hazardous=1
        $queryBuilder = $nearEarthObjectRepository->findAllQueryBuilder();

        $paginatedCollection = $this
            ->paginationFactory
            ->createCollection($queryBuilder, $request, 'neo_hazardous');

        $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
        $converter = new CamelCaseToSnakeCaseNameConverter();
        $normalizers = [
            new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d']),
            new ArrayDenormalizer(),
            new ObjectNormalizer(null, $converter, null, $extractor),
        ];
        $encoders = [new JsonEncoder()];

        $serializer = new Serializer($normalizers, $encoders);
        $serializedPaginatedCollection = $serializer->serialize($paginatedCollection, 'json');

        return new Response($serializedPaginatedCollection, 200, [
            'Content-Type' => 'application/json',
        ]);
    }
}

Sources:
Issue on github showing
The Symfony Serializer: a great, but complex component
UPD solution . Thanks again to BoShurik for the clarifications - if you initially install symfony/serializer-pack, then all packages will be installed immediately, the annotation @param NearEarthObject[] $itemsis not needed, but a piece
$extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
        $converter = new CamelCaseToSnakeCaseNameConverter();
        $normalizers = [
            new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d']),
            new ArrayDenormalizer(),
            new ObjectNormalizer(null, $converter, null, $extractor),
        ];
        $encoders = [new JsonEncoder()];

        $serializer = new Serializer($normalizers, $encoders);
        $serializedPaginatedCollection = $serializer->serialize($paginatedCollection, 'json');

        return new Response($serializedPaginatedCollection, 200, [
            'Content-Type' => 'application/json',
        ]);

reduced to and the serializer does all the work automatically
return $this->json($paginatedCollection);

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question