-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Description
Symfony version(s) affected
^6.0
Description
A TypeError is thrown by the Symfony serializer when trying to denormalize a string value into object.
class Author {
public $name;
}
class Blog {
public $title;
public Author $author;
}
// when there is no issue
$blog = $denormalizer->denormalize([
'title' => 'test title',
'author' => [
'name' => 'test name',
],
], Blog::class);
// when a type error is thrown
$blog = $denormalizer->denormalize([
'title' => 'test title',
'author' => 'test string',
], Blog::class);
The expected behavior is the deserializer throwing a Symfony\Component\Serializer\Exception\UnexpectedValueException
exception.
When recieving JSON through an API this can be quite annoying if you want to tell the user that they sent malformed json. You'd currently have to catch the ValueException which is not ideal.
How to reproduce
The issue can be reproducted by cloning this repository:
- clone this repo: https://github.com/JustDylan23/blog (
git clone git@github.com:JustDylan23/blog.git
) - run
git checkout bug-showcase
- follow the instructions in the read me
- navigate to http://localhost/bug
A type error should be thrown at this point.
TypeError:
Symfony\Component\Serializer\Normalizer\AbstractNormalizer::prepareForDenormalization(): Argument #1 ($data) must be of type object|array|null, string given, called in /var/www/symfony/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php on line 368
at vendor/symfony/serializer/Normalizer/AbstractNormalizer.php:299
at Symfony\Component\Serializer\Normalizer\AbstractNormalizer->prepareForDenormalization('test string')
(vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:368)
at Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalize('test string', 'App\\Entity\\User', null, array('cache_key' => 'c93a6d4efa206ea58a62cc6b7fab8dfb', 'deserialization_path' => 'author'))
(vendor/symfony/serializer/Serializer.php:238)
at Symfony\Component\Serializer\Serializer->denormalize('test string', 'App\\Entity\\User', null, array('cache_key' => 'c93a6d4efa206ea58a62cc6b7fab8dfb', 'deserialization_path' => 'author'))
(vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:559)
at Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->validateAndDenormalize(array(object(Type)), 'App\\Entity\\Blog', 'author', 'test string', null, array('cache_key' => '44db5a926a1544b1a8585af40107ca3a', 'deserialization_path' => 'author'))
(vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:401)
at Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalize(array('title' => 'test title', 'author' => 'test string'), 'App\\Entity\\Blog', null, array('cache_key' => '44db5a926a1544b1a8585af40107ca3a'))
(vendor/symfony/serializer/Serializer.php:238)
at Symfony\Component\Serializer\Serializer->denormalize(array('title' => 'test title', 'author' => 'test string'), 'App\\Entity\\Blog')
(src/Controller/BugReproductionController.php:19)
at App\Controller\BugReproductionController->test(object(Serializer))
(vendor/symfony/http-kernel/HttpKernel.php:152)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor/symfony/http-kernel/HttpKernel.php:74)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor/symfony/http-kernel/Kernel.php:202)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php:35)
at Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner->run()
(vendor/autoload_runtime.php:29)
at require_once('/var/www/symfony/vendor/autoload_runtime.php')
(public/index.php:5)
Possible Solution
I think a passable solution would be to add type validation in the Symfony\Component\Serializer\Normalizer\AbstractNormalizer::prepareForDenormalization
function
To give some extra context, the function is called here:
symfony/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
Lines 361 to 376 in cef3d5a
public function denormalize(mixed $data, string $type, string $format = null, array $context = []) | |
{ | |
if (!isset($context['cache_key'])) { | |
$context['cache_key'] = $this->getCacheKey($format, $context); | |
} | |
$allowedAttributes = $this->getAllowedAttributes($type, $context, true); | |
$normalizedData = $this->prepareForDenormalization($data); | |
$extraAttributes = []; | |
$reflectionClass = new \ReflectionClass($type); | |
$object = $this->instantiateObject($normalizedData, $type, $context, $reflectionClass, $allowedAttributes, $format); | |
$resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); | |
foreach ($normalizedData as $attribute => $value) { | |
$attributeContext = $this->getAttributeDenormalizationContext($resolvedClass, $attribute, $context); |
Without the fix:
/**
* Normalizes the given data to an array. It's particularly useful during
* the denormalization process.
*/
protected function prepareForDenormalization(object|array|null $data): array
{
return (array) $data;
}
With the fix
/**
* Normalizes the given data to an array. It's particularly useful during
* the denormalization process.
*/
protected function prepareForDenormalization(mixed $data): array
{
if (is_scalar($data)) {
throw new Symfony\Component\Serializer\Exception\UnexpectedValueException();
}
return (array) $data;
}
Additional Context
No response