I have a content-type with an address field on it. The field is optional. I cannot send null
as a value for that field or I get a 500 internal server error.
This happens on create (POST
) or update (PATCH
). I would expect to be able to remove such an optional field value from a node by sending a null
value in a request.
I can successfully create a node if I pass in a valid address field or do not supply one at all. BUT in the case that I don't supply a value then the serialized response from the POST will contain the field attribute with a value of null
. I would expect that if I changed an attribute value in such a result and send the content the server gave me directly back for an update (PATCH) it would succeed (say I just change the title of the node, the server should be able to deserialize what it serialized to me) but I can't because the server result contains null
so my client needs to know to delete the field if the property is set and has a null
value to avoid the 500 internal server error.
At this point it seems I simply cannot remove an optional address from the node since I cannot send an empty address (the server will complain that is invalid) and I cannot send null
to have it completely removed. I haven't yet tried deleting the property on PATCH
but this would go against the grain of the verb being a sparse UPDATE
.
I.e. per the JSON API specification for PATCH
If a request does not include all of the attributes for a resource, the server MUST interpret the missing attributes as if they were included with their current values. The server MUST NOT interpret missing attributes as null values.
The stack trace:
Error: Call to a member function getClass() on null in /drupal/web/modules/contrib/jsonapi/src/Normalizer/FieldItemNormalizer.php on line 117 #0 /drupal/vendor/symfony/serializer/Serializer.php(182): Drupal\\jsonapi\\Normalizer\\FieldItemNormalizer->denormalize(NULL, 'Drupal\\\\address\\\\...', 'api_json', Array)\n#1 /drupal/web/modules/contrib/jsonapi/src/Serializer/Serializer.php(75): Symfony\\Component\\Serializer\\Serializer->denormalize(NULL, 'Drupal\\\\address\\\\...', 'api_json', Array)\n#2 /drupal/web/modules/contrib/jsonapi/src/Normalizer/FieldNormalizer.php(65): Drupal\\jsonapi\\Serializer\\Serializer->denormalize(NULL, 'Drupal\\\\address\\\\...', 'api_json', Array)\n#3 /drupal/vendor/symfony/serializer/Serializer.php(182): Drupal\\jsonapi\\Normalizer\\FieldNormalizer->denormalize(NULL, '\\\\Drupal\\\\Core\\\\Fi...', 'api_json', Array)\n#4 /drupal/web/modules/contrib/jsonapi/src/Serializer/Serializer.php(75): Symfony\\Component\\Serializer\\Serializer->denormalize(NULL, '\\\\Drupal\\\\Core\\\\Fi...', 'api_json', Array)\n#5 /drupal/web/modules/contrib/jsonapi/src/Normalizer/ContentEntityDenormalizer.php(77): Drupal\\jsonapi\\Serializer\\Serializer->denormalize(NULL, '\\\\Drupal\\\\Core\\\\Fi...', 'api_json', Array)\n#6 /drupal/web/modules/contrib/jsonapi/src/Normalizer/EntityDenormalizerBase.php(99): Drupal\\jsonapi\\Normalizer\\ContentEntityDenormalizer->prepareInput(Array, Object(Drupal\\jsonapi\\ResourceType\\ResourceType), 'api_json', Array)\n#7 /drupal/vendor/symfony/serializer/Serializer.php(182): Drupal\\jsonapi\\Normalizer\\EntityDenormalizerBase->denormalize(Array, 'Drupal\\\\node\\\\Ent...', 'api_json', Array)\n#8 /drupal/web/modules/contrib/jsonapi/src/Serializer/Serializer.php(75): Symfony\\Component\\Serializer\\Serializer->denormalize(Array, 'Drupal\\\\node\\\\Ent...', 'api_json', Array)\n#9 /drupal/web/modules/contrib/jsonapi/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php(169): Drupal\\jsonapi\\Serializer\\Serializer->denormalize(Array, 'Drupal\\\\node\\\\Ent...', 'api_json', Array)\n#10 /drupal/vendor/symfony/serializer/Serializer.php(182): Drupal\\jsonapi\\Normalizer\\JsonApiDocumentTopLevelNormalizer->denormalize(Array, 'Drupal\\\\node\\\\Ent...', 'api_json', Array)\n#11 /drupal/web/modules/contrib/jsonapi/src/Serializer/Serializer.php(75): Symfony\\Component\\Serializer\\Serializer->denormalize(Array, 'Drupal\\\\jsonapi\\\\...', 'api_json', Array)\n#12 /drupal/web/modules/contrib/jsonapi/src/Controller/EntityResource.php(820): Drupal\\jsonapi\\Serializer\\Serializer->denormalize(Array, 'Drupal\\\\jsonapi\\\\...', 'api_json', Array)\n#13 /drupal/web/modules/contrib/jsonapi/src/Controller/EntityResource.php(306): Drupal\\jsonapi\\Controller\\EntityResource->deserialize(Object(Drupal\\jsonapi\\ResourceType\\ResourceType), Object(Symfony\\Component\\HttpFoundation\\Request), 'Drupal\\\\jsonapi\\\\...')\n#14 [internal function]: Drupal\\jsonapi\\Controller\\EntityResource->patchIndividual(Object(Drupal\\jsonapi\\ResourceType\\ResourceType), Object(Drupal\\node\\Entity\\Node), Object(Symfony\\Component\\HttpFoundation\\Request))\n#15 /drupal/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array(Array, Array)\n#16 /drupal/web/core/lib/Drupal/Core/Render/Renderer.php(582): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#17 /drupal/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\\Core\\Render\\Renderer->executeInRenderContext(Object(Drupal\\Core\\Render\\RenderContext), Object(Closure))\n#18 /drupal/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array)\n#19 /drupal/vendor/symfony/http-kernel/HttpKernel.php(151): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#20 /drupal/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw(Object(Symfony\\Component\\HttpFoundation\\Request), 1)\n#21 /drupal/web/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#22 /drupal/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#23 /drupal/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(99): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#24 /drupal/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(78): Drupal\\page_cache\\StackMiddleware\\PageCache->pass(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#25 /drupal/web/modules/contrib/jsonapi/src/StackMiddleware/FormatSetter.php(45): Drupal\\page_cache\\StackMiddleware\\PageCache->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#26 /drupal/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\jsonapi\\StackMiddleware\\FormatSetter->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#27 /drupal/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#28 /drupal/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#29 /drupal/web/core/lib/Drupal/Core/DrupalKernel.php(693): Stack\\StackedHttpKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#30 /drupal/web/index.php(19): Drupal\\Core\\DrupalKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request))
I'm upgrading from 1x and I must say I'm actually running into a lot of 500 internal server errors relating to property definitions within complex fields. So far I've been adding debug code to FieldItemNormalizer to figure out what's causing the server to choke and working around issues like this.
In this case if I add a few dd statements in the offending code:
public function denormalize($data, $class, $format = NULL, array $context = []) {
...
dd($class);
if (!is_array($data)) {
$property_value = $data;
$property_name = $item_definition->getMainPropertyName();
dd($property_name.'='.isset($property_definitions[$property_name]));
$property_value_class = $property_definitions[$property_name]->getClass();
return $denormalize_property($property_name, $property_value, $property_value_class, $format, $context);
}
...
}
The resulting output is:Drupal\address\Plugin\Field\FieldType\AddressItem
followed by=
so $property_name is empty as is its value in $property_definitions.