Problem/Motivation
Passing optional SDC properties are with a NULL value triggers an error like the following:
NULL value found, but a number or an object is required. This may be because the property is empty instead of having data present.
This error message is correct, since per the JSON Schema documentation on required properties:
In JSON a property with value
nullis not equivalent to the property not being present.
See also case 6 in the JSON Schema tour of required properties, which shows that a null value for an optional property that doesn't accept null values is invalid.
If a value is present, it must be of the allowed types.
Steps to reproduce
1. Run the composer require justinrainbow/json-schema command. We need a validator so that the \Drupal\Core\Theme\Component\ComponentValidator::validateProps can run its logic.
2. Create a component and add an optional property to it, for example
$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json
version: 1.0
name: Error
status: stable
props:
properties:
text:
type: string
title: Text
3. Render new component:
$build = [
'#type' => 'component',
'#component' => 'module:component_name',
'#props' => [
'text' => NULL,
]
];
Proposed resolution
Provide a utility method that, given a data structure representing a component, tests the values against the component's schema and removes any properties for which the following are all true:
- The property is optional.
- A null value is provided.
- null is not a type defined in the property's schema.
Developers could, optionally, invoke that method on data before passing it to rendering.
Remaining tasks
User interface changes
Introduced terminology
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #7 | 3531905-null-error-option-properties.patch | 1.51 KB | foxy-vikvik |
Issue fork drupal-3531905
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
quietone commentedIf this problem was discovered on a version of Drupal that is not 11.x, add that information in the issue summary and leave the version at 11.x. In Drupal core changes are made on on 11.x (our main development branch) first, and are then back ported as needed according to the Core change policies. Also mentioned on the version section of the list of issue fields documentation.
Comment #4
alt.dev commentedAdded the fix for the problem. A test for the case is needed.
Comment #5
nedjoThanks for reporting this issue. While the patch adds
nullas a valid type for top-level non-required properties, those properties may themselves have child properties that are not yet handled.Comment #6
nedjoInstead of adding null as a type, can we instead skip validation for properties that are both optional and null?
Comment #7
foxy-vikvik commentedWorks fine with Drupal 10
Comment #8
phenaproxima@lauriii explained this issue to me on Zoom. Basically: optional properties (anything not explicitly defined as required) should be allowed to default to null.
I don't know what the correct fix is, and I have no opinion on that. But you're going to need test coverage of whatever that fix is, so self-assigning to write a test.
Comment #9
mherchelI want to add that I run into this issue all the time, and also fix it by adding
'null'as a type.Comment #10
pdureau commentedI didn't look in details yet but I have the feeling there may be something wrong with this proposal. There may be a root cause to address instead.
If the property is optional, why putting a NULL value instead of an empty value?
Let's try to not alter the prop definitions too much. We have already a similar hack to get rid off:
https://git.drupalcode.org/project/drupal/-/blob/11.x/core/lib/Drupal/Co...
Comment #11
lauriiiThis isn't really all that relevant when using SDC in render array but when using SDCs within Twig. Twig doesn't have the concept of undefined unless you use Twig
strict_variablesturned on (which Drupal is not doing). So when you are passing inundefinedproperties, they becomenull. I don't think there's a practical way to keep the variables asundefinedand really as a frontend developer, you shouldn't have to care.An example of this would be this card component. The URL is optional and when it's not defined, it becomes
nullfor heading which triggers the validation error.Comment #12
pdureau commentedthanks fo the additional context. I will do some tests early next week.
Comment #13
pdureau commentedFresh Drupal 11.2.4 install, standard profile. Only changes:
composer require justinrainbow/json-schema$settings['extension_discovery_scan_tests'] = TRUE;in settings.phpCalling sdc_theme_test:my-card component from this issue's MR, which has three optional props:
I haven't noticed anything wrong for now: https://github.com/pdureau/oviedo/blob/master/templates/page.html.twig
I will do a second batch of tests later (to be sure JSON Schema validator is effectively triggered).
Comment #14
lauriiiYou would run into the problem if you provided
sloganas an optional property of the page which passes it to the card from the page. This would not trigger validation errors when slogan is provided but would trigger the validation error when page is not receivingslogan.Comment #15
pdureau commentedIf page has properties, I understand there are 2 components here:
cardandpage.So, I will do my next test with:
oviedo:pagecomponentoviedo:pagecomponent (an exact copy ofsdc_theme_test:my-cardfrom this issue's MR) includingsdc_theme_test:my-cardcomponent (many times): https://github.com/pdureau/oviedo/blob/master/components/page/page.twigComment #16
pameeela commentedComment #18
pdureau commentedLet's push results of #15
Comment #19
penyaskitoThe test failure is because we are not recursing any object that we could have (and it could be another object embedding another object, etc).
This is not a bug in SDC but in the component, prop = NULL is not the same than omitting a prop. which wouldn't trigger any error. But I agree this is terrible DX when using components inside other components because of twig, and should be solved.
Comment #20
penyaskitoComment #21
pdureau commentedBeing unable to reproduce the issue and, to be honest, even to fully understand it , I still believe we are not addressing the root cause here but doing a workaround.
However, I guess I am OK for it anyway because:
ComponentValidatorinstead ofComponentMetadataso it seems we are not messing with the real component schema and we are not doing something like #3554720: Remove addition of a object type in all propsComment #22
pameeela commentedThis no longer affects Mercury so removing the tag although I understand this is an overall DX improvement so may be a good idea anyway.
Comment #23
wim leersI was pointed here by @penyaskito from #3555413-32: Allow linking to referenced entities: add `url` property to `EntityReferenceItem::propertyDefinitions()`, after I ran into the very same problem over there (see #3541361-27: Find optional field instance matches for `type: object` props (images + videos), including for optional fields on bundleless entity types (e.g. `User`'s `user_picture`) for details).
I was going to fix this as part of #3541361: Find optional field instance matches for `type: object` props (images + videos), including for optional fields on bundleless entity types (e.g. `User`'s `user_picture`), since it's already doing something related.
I'm not convinced this change is necessary — it can be left up to the caller to just not pass NULL values. And in Canvas, we could quite easily achieve that:
Comment #24
mherchelIt can... but it's not always easy.
We frequently call components from templates where props may or may not be populated. If they're not populated, a
nullis passed, which makes us have to add"null"as a type (which is bad DX).I feel that the the DX wins here far outweigh any negatives (which TBH, i don't see any negatives)
Comment #26
idiaz.roncero+1 to this.
I also think that leaving out to the caller (the developer) not to pass NULL values leads us to constructs that are ugly and cumbersome.
Some real, recent examples for a paragraph that uses Paragraph Behaviors to feed some settings that map almost 1:1 to SDC props.
We basically need to repeat an if/merge block for each prop that is not required in order to avoid NULL values from provoking a WSOD. They are a lot, so our twig code became an ugly mess:
Yes, this could have been cleaner on PHP, but not so much.
If you add this to the old problem of false positives on render arrays that only carry cache metadata, something as simple as mapping
content.field_whateverormyvaron twig and expecting it to be ignored when null / empty... is becoming excessively complex.I strongly feel we need to reduce the noise for the sake of DX (in general), and allowing null on optional props would clearly help, even if it is not 100% correct (as we will be basically altering the schema contract under the hood, as I understood).
Comment #27
nicxvan commentedWe just updated locally to 11.4 from 10.6 to test and many, many pages WSOD due to this error.
Even if it throws an error I don't think having the type wrong on a prop should escalate to a WSOD.
Comment #28
f0ns commentedCore already adds
'object'to the type of every prop, inComponentMetadata::parseSchemaInfo(). We can add'null'the same way, for props that aren't required.About the white screens: this check only runs inside
assert(). If you get it on production, assertions are on there, which is a different problem. Setzend.assertions=-1and it's gone. In dev it does break the whole page, because the exception is thrown while Twig is rendering. We could catch it indoValidateProps()and just log it instead, and keep it strict for core's own tests. Maybe as a follow-up issue.Until then, the workaround per prop is
type: [string, 'null'], or|default('')where you use it.Comment #29
pdureau commentedNot anymore: #3554720: Remove addition of a object type in all props
Let's try to not mess too much with JSON schema of prop definitions. Especially in
ComponentMetadata.As said in comment #21, if a fix is needed, it can be done in
ComponentValidatorinstead.Comment #30
pdureau commented@idiaz.roncero
Altering mapping (associative arrays) in Twig is not a casual operations. It is complicated with or without SDC.
What was wrong with a classic props management?
Comment #31
nicxvan commentedThey are optional, them missing should be expected, needing to add a default every call means it's not optional. It's just required with extra steps.
It also doesn't tell you where it was included from, and a WSOD is an extreme response to a mistyped prop.
Comment #32
idiaz.roncero@pdureau
This line
Will fail when
behavior.alignmentevaluates to null (because it has no value, because its value is null/undefined on purpose, whatever) unless you either add "null" as an explicit type.It is common that optional values could and should be present but null on twig. Fields come to mind as a good example.
Yep, there are many ways to fix it before it reaches SDC; but I also feel that accepting null for optional values is a better DX than having to manually handle the inclusion or not of some variables to circumvent this.
Comment #33
nedjoWe validate our props against JSON Schema. Per the JSON Schema documentation on required properties:
See also case 6 in the JSON Schema tour of required properties, which shows that a null value for an optional property that doesn't accept null values is invalid.
Therefore, there isn't a bug here. Instead, the validation error given in the issue summary is appropriate and correct: if a value is present, it must be of the allowed types. The ideal fix is of the sort @wim leers suggested in #23: don't pass
NULLvalues from the caller.Before we close this as won't fix, though, it's worth asking: is there anything we could do to address the DX issue while maintaining a distinction between optional properties and null values?
I suppose we might provide a utility method that, given a data structure representing a component, tests the values against the component's schema and removes any properties for which the following are all true:
Developers could, optionally, invoke that method on data before passing the result to rendering.
Therefore, switching this from a bug report to a feature request and updating the summary accordingly.
Comment #34
nicxvan commentedI think this is the primary thing and there are multiple aspects.
1. There is a WSOD if there is a data discrepancy.
2. Something should strip these parameters if they are null, having to handle null on every single optional parameter is a terrible DX.
3. I would expect some type juggling to happen, IIRC it's complaining that 1 is a string instead of boolean in some cases and it's boolean instead of an object in others.