Обработка второстепенных данных во время десериализации при использовании компонента Symfony Serializer

#symfony #json-deserialization

#symfony #json-десериализация

Вопрос:

Я новичок в компоненте Symfony serializer. Я пытаюсь правильно десериализовать тело JSON в следующий DTO:

 class PostDTO
{
    /** @var string */
    private $name;

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @param string $name
     */
    public function setName(string $name): void
    {
        $this->name = $name;
    }
}
  

Контроллер использует следующий метод:

 /**
 * @Route (path="", methods={"POST"}, name="new_post")
 * @param Request $request
 * @return Response
 */
public function create(Request $request): Response
{
    $model = $this->serializer->deserialize($request->getContent(), PostDTO::class, 'json');
    // call the service with the model
    return new JsonResponse();
}
  

Моя проблема в том, что я хотел обработать бизнес-проверку после десериализации тела. Однако, если я укажу недопустимое значение для имени, например, false или [] , десериализация завершится ошибкой с исключением: SymfonyComponentSerializerExceptionNotNormalizableValueException: "The type of the "name" attribute for class "AppServicePostDTO" must be one of "string" ("array" given). .

Я понимаю, что это потому, что я намеренно установил "name": [] . Тем не менее, я искал способ установить для полей значение по умолчанию или даже выполнить некоторую предварительную десериализацию проверки.

Ответ №1:

Я нашел правильный способ справиться с этим. Это исключение было вызвано тем, что сериализатор не смог создать PostDTO класс, используя недопустимую полезную нагрузку, которую я предоставил.

Чтобы справиться с этим, я создал свой пользовательский денормализатор, который запускается только для этого конкретного класса. Для этого я реализовал DenormalizerInterface примерно так:

 use AppServicePostDTO;
use SymfonyComponentSerializerExceptionExceptionInterface;
use SymfonyComponentSerializerNormalizerDenormalizerInterface;
use SymfonyComponentSerializerNormalizerObjectNormalizer;

class PostDTODeserializer implements DenormalizerInterface
{
    /** @var ObjectNormalizer */
    private $normalizer;

    /**
     * PostDTODeserializer constructor.
     * @param ObjectNormalizer $normalizer
     */
    public function __construct(ObjectNormalizer $normalizer)
    {
        $this->normalizer = $normalizer;
    }

    public function denormalize($data, string $type, string $format = null, array $context = [])
    {
        return $type === PostDTO::class;
    }

    /**
     * @param mixed $data
     * @param string $type
     * @param string|null $format
     * @return array|bool|object
     * @throws ExceptionInterface
     */
    public function supportsDenormalization($data, string $type, string $format = null)
    {
        // validate the array which will be normalized (you should write your validator and inject it through the constructor)
        if (!is_string($data['name'])) {
            // normally you would throw an exception and leverage the `ErrorController` functionality

            // do something
        }

        // convert the array to the object
        return $this->normalizer->denormalize($data, $type, $format);
    }
}
  

Если вы хотите получить доступ к context массиву, вы можете реализовать DenormalizerAwareInterface . Обычно вы создаете свою пользовательскую проверку, вводите ее в этот денормализатор и проверяете $data массив.

Пожалуйста, не думайте, что я ввел ObjectNormalizer here, чтобы, когда данные успешно прошли проверку, я все равно мог создать с PostDTO помощью $data .

PS: в моем случае автоматическое подключение автоматически зарегистрировало мой пользовательский денормализатор. Если ваш компонент не подключен автоматически, перейдите services.yaml и добавьте следующие строки:

  AppSerializerPostDTODeserializer:
        tags: ['serializer.normalizer']
  

(Я пометил реализацию serializer.normalizer so, поскольку она распознается во время конвейера десериализации)