It seems that only the first property type is checked in ApiPlatform\Serializer\AbstractItemNormalizer::createAndValidateAttributeValue() method.
It could be interesting to check the multiples types of each property.
<?php
namespace App\ApiResource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use App\Dto\FooInput;
#[ApiResource(
operations: [
new Post(
uriTemplate: '/foo',
),
],
)]
class Foo
{
public string|int|null $bar;
}
{
"@context": "/api/contexts/Error",
"@type": "hydra:Error",
"hydra:title": "An error occurred",
"hydra:description": "The type of the \"bar\" attribute must be \"string\", \"integer\" given.",
"trace": [
{
"namespace": "",
"short_class": "",
"class": "",
"type": "",
"function": "",
"file": "/home/Workspace/api-platform/vendor/symfony/serializer/Exception/NotNormalizableValueException.php",
"line": 31,
"args": []
},
{
"namespace": "Symfony\\Component\\Serializer\\Exception",
"short_class": "NotNormalizableValueException",
"class": "Symfony\\Component\\Serializer\\Exception\\NotNormalizableValueException",
"type": "::",
"function": "createForUnexpectedDataType",
"file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
"line": 467,
"args": []
},
{
"namespace": "ApiPlatform\\Serializer",
"short_class": "AbstractItemNormalizer",
"class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
"type": "->",
"function": "validateType",
"file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
"line": 840,
"args": []
},
{
"namespace": "ApiPlatform\\Serializer",
"short_class": "AbstractItemNormalizer",
"class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
"type": "->",
"function": "createAndValidateAttributeValue",
"file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
"line": 711,
"args": []
},
{
"namespace": "ApiPlatform\\Serializer",
"short_class": "AbstractItemNormalizer",
"class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
"type": "->",
"function": "createAttributeValue",
"file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
"line": 443,
"args": []
},
{
"namespace": "ApiPlatform\\Serializer",
"short_class": "AbstractItemNormalizer",
"class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
"type": "->",
"function": "setAttributeValue",
"file": "/home/Workspace/api-platform/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php",
"line": 382,
"args": []
},
{
"namespace": "Symfony\\Component\\Serializer\\Normalizer",
"short_class": "AbstractObjectNormalizer",
"class": "Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer",
"type": "->",
"function": "denormalize",
"file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
"line": 219,
"args": []
},
{
"namespace": "ApiPlatform\\Serializer",
"short_class": "AbstractItemNormalizer",
"class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
"type": "->",
"function": "denormalize",
"file": "/home/Workspace/api-platform/vendor/api-platform/core/src/JsonLd/Serializer/ItemNormalizer.php",
"line": 150,
"args": []
},
{
"namespace": "ApiPlatform\\JsonLd\\Serializer",
"short_class": "ItemNormalizer",
"class": "ApiPlatform\\JsonLd\\Serializer\\ItemNormalizer",
"type": "->",
"function": "denormalize",
"file": "/home/Workspace/api-platform/vendor/symfony/serializer/Serializer.php",
"line": 227,
"args": []
},
{
"namespace": "Symfony\\Component\\Serializer",
"short_class": "Serializer",
"class": "Symfony\\Component\\Serializer\\Serializer",
"type": "->",
"function": "denormalize",
"file": "/home/Workspace/api-platform/vendor/symfony/serializer/Serializer.php",
"line": 145,
"args": []
},
{
"namespace": "Symfony\\Component\\Serializer",
"short_class": "Serializer",
"class": "Symfony\\Component\\Serializer\\Serializer",
"type": "->",
"function": "deserialize",
"file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Symfony/EventListener/DeserializeListener.php",
"line": 101,
"args": []
},
{
"namespace": "ApiPlatform\\Symfony\\EventListener",
"short_class": "DeserializeListener",
"class": "ApiPlatform\\Symfony\\EventListener\\DeserializeListener",
"type": "->",
"function": "onKernelRequest",
"file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/Debug/WrappedListener.php",
"line": 115,
"args": []
},
{
"namespace": "Symfony\\Component\\EventDispatcher\\Debug",
"short_class": "WrappedListener",
"class": "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener",
"type": "->",
"function": "__invoke",
"file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/EventDispatcher.php",
"line": 206,
"args": []
},
{
"namespace": "Symfony\\Component\\EventDispatcher",
"short_class": "EventDispatcher",
"class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
"type": "->",
"function": "callListeners",
"file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/EventDispatcher.php",
"line": 56,
"args": []
},
{
"namespace": "Symfony\\Component\\EventDispatcher",
"short_class": "EventDispatcher",
"class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
"type": "->",
"function": "dispatch",
"file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php",
"line": 127,
"args": []
},
{
"namespace": "Symfony\\Component\\EventDispatcher\\Debug",
"short_class": "TraceableEventDispatcher",
"class": "Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher",
"type": "->",
"function": "dispatch",
"file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/HttpKernel.php",
"line": 139,
"args": []
},
{
"namespace": "Symfony\\Component\\HttpKernel",
"short_class": "HttpKernel",
"class": "Symfony\\Component\\HttpKernel\\HttpKernel",
"type": "->",
"function": "handleRaw",
"file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/HttpKernel.php",
"line": 74,
"args": []
},
{
"namespace": "Symfony\\Component\\HttpKernel",
"short_class": "HttpKernel",
"class": "Symfony\\Component\\HttpKernel\\HttpKernel",
"type": "->",
"function": "handle",
"file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/Kernel.php",
"line": 184,
"args": []
},
{
"namespace": "Symfony\\Component\\HttpKernel",
"short_class": "Kernel",
"class": "Symfony\\Component\\HttpKernel\\Kernel",
"type": "->",
"function": "handle",
"file": "/home/Workspace/api-platform/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php",
"line": 35,
"args": []
},
{
"namespace": "Symfony\\Component\\Runtime\\Runner\\Symfony",
"short_class": "HttpKernelRunner",
"class": "Symfony\\Component\\Runtime\\Runner\\Symfony\\HttpKernelRunner",
"type": "->",
"function": "run",
"file": "/home/Workspace/api-platform/vendor/autoload_runtime.php",
"line": 29,
"args": []
},
{
"namespace": "",
"short_class": "",
"class": "",
"type": "",
"function": "require_once",
"file": "/home/Workspace/api-platform/public/index.php",
"line": 5,
"args": [
[
"string",
"/home/Workspace/api-platform/vendor/autoload_runtime.php"
]
]
}
]
}
This behaviour does not appear when using a DTO as input class.
Description
Hi,
It seems that only the first property type is checked in ApiPlatform\Serializer\AbstractItemNormalizer::createAndValidateAttributeValue() method.
It could be interesting to check the multiples types of each property.
How to reproduce
Given the following Resource:
When I execute the request with a string
then the response is OK
But when I execute the request with an integer
Then I've got the following 400 error:
{ "@context": "/api/contexts/Error", "@type": "hydra:Error", "hydra:title": "An error occurred", "hydra:description": "The type of the \"bar\" attribute must be \"string\", \"integer\" given.", "trace": [ { "namespace": "", "short_class": "", "class": "", "type": "", "function": "", "file": "/home/Workspace/api-platform/vendor/symfony/serializer/Exception/NotNormalizableValueException.php", "line": 31, "args": [] }, { "namespace": "Symfony\\Component\\Serializer\\Exception", "short_class": "NotNormalizableValueException", "class": "Symfony\\Component\\Serializer\\Exception\\NotNormalizableValueException", "type": "::", "function": "createForUnexpectedDataType", "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php", "line": 467, "args": [] }, { "namespace": "ApiPlatform\\Serializer", "short_class": "AbstractItemNormalizer", "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer", "type": "->", "function": "validateType", "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php", "line": 840, "args": [] }, { "namespace": "ApiPlatform\\Serializer", "short_class": "AbstractItemNormalizer", "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer", "type": "->", "function": "createAndValidateAttributeValue", "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php", "line": 711, "args": [] }, { "namespace": "ApiPlatform\\Serializer", "short_class": "AbstractItemNormalizer", "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer", "type": "->", "function": "createAttributeValue", "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php", "line": 443, "args": [] }, { "namespace": "ApiPlatform\\Serializer", "short_class": "AbstractItemNormalizer", "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer", "type": "->", "function": "setAttributeValue", "file": "/home/Workspace/api-platform/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php", "line": 382, "args": [] }, { "namespace": "Symfony\\Component\\Serializer\\Normalizer", "short_class": "AbstractObjectNormalizer", "class": "Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer", "type": "->", "function": "denormalize", "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php", "line": 219, "args": [] }, { "namespace": "ApiPlatform\\Serializer", "short_class": "AbstractItemNormalizer", "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer", "type": "->", "function": "denormalize", "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/JsonLd/Serializer/ItemNormalizer.php", "line": 150, "args": [] }, { "namespace": "ApiPlatform\\JsonLd\\Serializer", "short_class": "ItemNormalizer", "class": "ApiPlatform\\JsonLd\\Serializer\\ItemNormalizer", "type": "->", "function": "denormalize", "file": "/home/Workspace/api-platform/vendor/symfony/serializer/Serializer.php", "line": 227, "args": [] }, { "namespace": "Symfony\\Component\\Serializer", "short_class": "Serializer", "class": "Symfony\\Component\\Serializer\\Serializer", "type": "->", "function": "denormalize", "file": "/home/Workspace/api-platform/vendor/symfony/serializer/Serializer.php", "line": 145, "args": [] }, { "namespace": "Symfony\\Component\\Serializer", "short_class": "Serializer", "class": "Symfony\\Component\\Serializer\\Serializer", "type": "->", "function": "deserialize", "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Symfony/EventListener/DeserializeListener.php", "line": 101, "args": [] }, { "namespace": "ApiPlatform\\Symfony\\EventListener", "short_class": "DeserializeListener", "class": "ApiPlatform\\Symfony\\EventListener\\DeserializeListener", "type": "->", "function": "onKernelRequest", "file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/Debug/WrappedListener.php", "line": 115, "args": [] }, { "namespace": "Symfony\\Component\\EventDispatcher\\Debug", "short_class": "WrappedListener", "class": "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener", "type": "->", "function": "__invoke", "file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/EventDispatcher.php", "line": 206, "args": [] }, { "namespace": "Symfony\\Component\\EventDispatcher", "short_class": "EventDispatcher", "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher", "type": "->", "function": "callListeners", "file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/EventDispatcher.php", "line": 56, "args": [] }, { "namespace": "Symfony\\Component\\EventDispatcher", "short_class": "EventDispatcher", "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher", "type": "->", "function": "dispatch", "file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php", "line": 127, "args": [] }, { "namespace": "Symfony\\Component\\EventDispatcher\\Debug", "short_class": "TraceableEventDispatcher", "class": "Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher", "type": "->", "function": "dispatch", "file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/HttpKernel.php", "line": 139, "args": [] }, { "namespace": "Symfony\\Component\\HttpKernel", "short_class": "HttpKernel", "class": "Symfony\\Component\\HttpKernel\\HttpKernel", "type": "->", "function": "handleRaw", "file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/HttpKernel.php", "line": 74, "args": [] }, { "namespace": "Symfony\\Component\\HttpKernel", "short_class": "HttpKernel", "class": "Symfony\\Component\\HttpKernel\\HttpKernel", "type": "->", "function": "handle", "file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/Kernel.php", "line": 184, "args": [] }, { "namespace": "Symfony\\Component\\HttpKernel", "short_class": "Kernel", "class": "Symfony\\Component\\HttpKernel\\Kernel", "type": "->", "function": "handle", "file": "/home/Workspace/api-platform/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php", "line": 35, "args": [] }, { "namespace": "Symfony\\Component\\Runtime\\Runner\\Symfony", "short_class": "HttpKernelRunner", "class": "Symfony\\Component\\Runtime\\Runner\\Symfony\\HttpKernelRunner", "type": "->", "function": "run", "file": "/home/Workspace/api-platform/vendor/autoload_runtime.php", "line": 29, "args": [] }, { "namespace": "", "short_class": "", "class": "", "type": "", "function": "require_once", "file": "/home/Workspace/api-platform/public/index.php", "line": 5, "args": [ [ "string", "/home/Workspace/api-platform/vendor/autoload_runtime.php" ] ] } ] }Additional Context
This behaviour does not appear when using a DTO as input class.