Problem/Motivation
Normalizers that provide JSON Schema disagree on how to describe optional data. See the change record for the JSON Schema support added to the serializer.
PrimitiveDataNormalizer::getNormalizationSchema()checksisRequired()and adds anullbranch for optional data.DateTimeNormalizer,TimestampNormalizer, andDateTimeIso8601Normalizeruse the defaultgetNormalizationSchema()fromSchematicNormalizerTraitorSchematicNormalizerHelperTrait. It reflects theJsonSchemaattribute and never checks the definition.DateTimeNormalizer::doNormalize()returnsNULLwhen there is no date, so the schema rejects valid output.TypedDataNormalizer::getNormalizationSchema()checksisRequired(), but returns invalid JSON Schema for stringable values.oneOftakes an array of schemas, not type names.
// TypedDataNormalizer::getNormalizationSchema()
return $nullable
? ['oneOf' => ['string', 'null']]
: ['type' => 'string'];
Steps to reproduce
$serializer = \Drupal::service('serializer');
$typed_data_manager = \Drupal::typedDataManager();
$string = $typed_data_manager->create(DataDefinition::create('string'));
$serializer->getJsonSchema($string, []);
// ['oneOf' => [['type' => 'string'], ['type' => 'null']]]
$timestamp = $typed_data_manager->create(DataDefinition::create('timestamp'));
$serializer->getJsonSchema($timestamp, []);
// ['type' => 'string', 'format' => 'date-time']
$serializer->normalize($timestamp);
// NULL
Both definitions are optional. Only the string schema allows null.
Proposed resolution
Decide nullability in one place and pass it to getJsonSchemaForMethod() from both default getNormalizationSchema() implementations. Use the rule PrimitiveDataNormalizer already follows: optional typed data is nullable, and so is a class name, because there is no definition to check.
protected function isNullable(mixed $object): bool {
return is_string($object)
|| ($object instanceof TypedDataInterface && !$object->getDataDefinition()->isRequired());
}
// SchematicNormalizerTrait
protected function getNormalizationSchema(mixed $object, array $context = []): array {
return $this->getJsonSchemaForMethod(
$this,
'doNormalize',
['$comment' => static::generateNoSchemaAvailableMessage($object)],
$this->isNullable($object),
);
}
PrimitiveDataNormalizer and TypedDataNormalizer switch to isNullable(). TypedDataNormalizer also fixes its stringable branch:
return $nullable
? ['oneOf' => [['type' => 'string'], ['type' => 'null']]]
: ['type' => 'string'];
Remaining tasks
- Merge request with test coverage for optional and required data in each normalizer.
User interface changes
None.
Introduced terminology
None.
API changes
JSON Schema for optional dates, timestamps, and stringable typed data allows null. Normalization output does not change.
Data model changes
None.
Release notes snippet
AI-Generated: Yes. I used Claude Code to turn my notes into this issue summary and to check the proposed resolution against the Drupal 11.4.6 source. I reviewed and edited the result. The proposed code is a sketch and has not been run.
Comments