Problem/Motivation
JsonSchemaProviderSerializerInterface::getJsonSchema() documents $object as "Object or interface/class name for which to retrieve a schema." Passing a class name throws a TypeError. See the change record for the JSON Schema support added to the serializer.
Two things stop a class name from reaching a normalizer:
- Symfony's
Serializer::normalize()resolves normalizers by type. A string resolves asnative-string, no normalizer claims it, and Symfony returns the string as is.JsonSchemaProviderSerializerTrait::getJsonSchema()then fails itsarrayreturn type. NormalizerBase::supportsNormalization()returnsFALSEfor anything that is not an object.
The normalizer side already expects class names. SchematicNormalizerHelperTrait::getNormalizationSchema() documents $object as "Supported object or class/interface name", JsonSchemaReflectionTrait::getJsonSchemaForMethod() calls class_exists(), code>PrimitiveDataNormalizer treats non-typed-data input as nullable, and JSON:API's FieldItemNormalizer::getNormalizationSchema() has an is_string($object) branch.
Steps to reproduce
\Drupal::service('serializer')->getJsonSchema(StringData::class, []);
// TypeError: Drupal\serialization\Serializer\Serializer::getJsonSchema():
// Return value must be of type array, string returned
Proposed resolution
Either make class names work or remove the claim.
Option 1: support class names. getJsonSchema() handles strings itself instead of going through normalize(). Symfony's normalizer list and getNormalizer() are private, so Drupal's Serializer has to keep its own reference to the normalizers. NormalizerBase::supportsNormalization() also needs a branch that accepts a class name for the json_schema format:
if ($format === 'json_schema' && is_string($data)) {
return (bool) array_filter($supported, fn ($name) => is_a($data, $name, TRUE));
}
A class name only determines the schema when the class alone decides the output. It works for StringData::class. It does not work for Map, ItemList, required values, or constraints, because those come from the data definition. JSON:API runs into this today: FieldNormalizer::getNormalizationSchema() appends a sample item, and ResourceObjectNormalizer creates a stub entity, because "normalizers are resolved by the class/object being normalized."
Option 2: remove the claim. Type $object as object on getJsonSchema() and update the docblocks on JsonSchemaProviderSerializerInterface, SchematicNormalizerHelperTrait, and JsonSchemaReflectionTrait.
Option 2 matches what works today. Generating a schema without instance data needs the definition, not the class, and could be a follow-up that accepts a DataDefinitionInterface.
Remaining tasks
- Pick an option.
- Merge request with test coverage.
User interface changes
None.
Introduced terminology
None.
API changes
Option 1: getJsonSchema() accepts class and interface names. Option 2: getJsonSchema() only accepts objects.
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.
Issue fork drupal-3627338
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
mglamanComment #3
dries commentedI looked into this a bit.
I'd take option 2 but would start with just the proposed change to the serializer. Passing a class name to
JsonSchemaProviderSerializerInterface::getJsonSchema()has never worked, and I can't find any tests for it.We could type the
$objectparameter ofgetJsonSchema()asobjectin Drupal 13? PHP would then reject a class name at the call. So I'd fix the docblock ongetJsonSchema()and deprecate passing an array.SchematicNormalizerHelperTrait::getNormalizationSchema()needs a docblock fix. Its docblock says it takes an object or a class name. The method is protected and on no interface. Core tests only pass it a class name, so the change should be safe.JsonSchemaReflectionTrait::getJsonSchemaForMethod()looks good already. Its docblock says it takes an object, so I don't think a change is required.Comment #4
dries commentedRelated: #3627331: Nullability is inconsistent across JSON Schema normalizers proposes an
isNullable()that treats a class name as nullable. The class-name docblock came from #3031367: Generate JSON schema for content entity types, where class names were raised in #3031367-32: Generate JSON schema for content entity types.Comment #6
dries commentedI opened a merge request and the test-only job failed as expected, just like on my localhost.
Does the array deprecation need a change record? Arrays were never documented as input, and no caller in Drupal Core uses arrays. The deprecation message links to this issue for now.
Disclaimer: I worked with Claude on the code and tests in the merge request.
Comment #7
dries commentedI created #3632107: Type the $object parameter of JsonSchemaProviderSerializerInterface::getJsonSchema() as object and added a @see-stament to the MR, but marked #3632107 postponed until this issue lands.
Comment #8
d34dman commented+1 for Option 2
My understandig is along this line as well. Schema that needs no instance is what "Rosetta" wants for tool schemas. Otherwise we might end up creating 'fake object' representation of a static schema for the sake of discovery.
Comment #9
dries commentedI drafted a change record at https://www.drupal.org/node/3632615 and updated the MR accordingly.
Comment #10
mglamanLooks good to me! Draft CR has been created as well.
Comment #11
mglaman