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 as native-string, no normalizer claims it, and Symfony returns the string as is. JsonSchemaProviderSerializerTrait::getJsonSchema() then fails its array return type.
  • NormalizerBase::supportsNormalization() returns FALSE for 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

Command icon 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

mglaman created an issue. See original summary.

mglaman’s picture

Issue summary: View changes
dries’s picture

I 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 $object parameter of getJsonSchema() as object in Drupal 13? PHP would then reject a class name at the call. So I'd fix the docblock on getJsonSchema() 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.

dries’s picture

dries’s picture

I 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.

dries’s picture

Status: Active » Needs review

I 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.

d34dman’s picture

+1 for Option 2

Generating a schema without instance data needs the definition, not the class,

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.

dries’s picture

I drafted a change record at https://www.drupal.org/node/3632615 and updated the MR accordingly.

mglaman’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me! Draft CR has been created as well.

mglaman’s picture

Issue tags: +rosetta