#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, поскольку она распознается во время конвейера десериализации)