Problem/Motivation

The serializer can describe typed data as JSON Schema. See the change record. There is no way back. Given a schema, the only option is to hand-write DataDefinition calls or walk the schema yourself.

Building definitions from a schema turns any published schema into request validation with the Typed Data API, including one generated by another Drupal site:

$definition = $factory->createFromJsonSchema($schema);
$data = \Drupal::typedDataManager()->create($definition, Json::decode($payload));
$violations = $data->validate();

The mapping is mechanical:

JSON Schema Typed data
type: object with properties MapDataDefinition with property definitions
required setRequired(TRUE) on each named property
type: array with items ListDataDefinition with setItemDefinition()
oneOf: [X, {type: null}] X, not required
type: string with format email, uri, decimal, datetime_iso8601, duration_iso8601
enum, minLength/maxLength, minimum/maximum, pattern AllowedValues, Length, Range, Regex

I have a working userland version that handles a nested object, a list, a nullable property, and each constraint above, then validates a payload against the result: from_json_schema.php.

Steps to reproduce

N/A

Proposed resolution

Add a factory that builds a DataDefinitionInterface from a JSON Schema array. Put it in the serialization module next to the rest of the JSON Schema support, instead of adding a method to TypedDataManagerInterface.

function from_json_schema(array $schema): DataDefinitionInterface {
  if (isset($schema['oneOf'])) {
    $branches = array_filter($schema['oneOf'], static fn (array $branch) => ($branch['type'] ?? NULL) !== 'null');
    if (count($branches) !== 1) {
      throw new \InvalidArgumentException('Only nullable oneOf schemas are supported.');
    }
    return from_json_schema(current($branches));
  }
  if ($schema['type'] === 'object') {
    $definition = MapDataDefinition::create();
    foreach ($schema['properties'] ?? [] as $name => $property_schema) {
      $property_definition = from_json_schema($property_schema);
      $property_definition->setRequired(in_array($name, $schema['required'] ?? [], TRUE));
      $definition->setPropertyDefinition($name, $property_definition);
    }
    return $definition;
  }
  if ($schema['type'] === 'array') {
    return ListDataDefinition::createFromDataType('list')
      ->setItemDefinition(from_json_schema($schema['items']));
  }
  $definition = DataDefinition::create(data_type_for_schema($schema['type'], $schema['format'] ?? NULL));
  add_constraints_from_schema($schema, $definition);
  return $definition;
}

Two gaps prevent an exact round trip, and the factory has to work around both today.

Schemas describe normalized output, not accepted input. timestamp stores an integer, but TimestampNormalizer reports format: date-time. TimestampNormalizer::denormalize() accepts that string, but TypedDataManager::create() sets values directly and skips denormalizers. A definition built from format: date-time has to use datetime_iso8601 instead of timestamp.

Formats have no registry. Mapping a format to a data type is a hardcoded match:

'string' => match ($format) {
  'email' => 'email',
  'uri' => 'uri',
  'number' => 'decimal',
  'date-time', 'date' => 'datetime_iso8601',
  'duration' => 'duration_iso8601',
  default => 'string',
},

Data types could declare the JSON Schema format they accept on the DataType attribute, so contrib data types take part.

Open questions:

  • Which keywords are in scope? $ref, allOf, anyOf, non-nullable oneOf, and additionalProperties have no direct typed data equivalent. Throwing on unsupported keywords is safer than ignoring them, because ignoring them makes validation more permissive than the schema.
  • JSON Schema pattern uses ECMA-262 syntax without delimiters. The Regex constraint needs a PCRE pattern with delimiters, so the factory has to escape the delimiter and add the u flag. This is the reverse of the Regex question in #3627330: Validation constraints do not contribute to the JSON Schema.
  • Should the constraint mapping reuse the interface proposed in #3627330: Validation constraints do not contribute to the JSON Schema, so each constraint knows how to read its own keywords?

Remaining tasks

  • Agree on scope and where the factory lives.
  • Decide whether the format registry belongs here or in a follow-up.
  • Merge request with test coverage.

User interface changes

None.

Introduced terminology

None.

API changes

New service that creates data definitions from JSON Schema. If the format registry is included, the DataType attribute gains a property for the JSON Schema format.

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 comes from a working example that runs against Drupal 11.4.6.

Comments

mglaman created an issue.