JSON:API can generate a JSON Schema for each field. I found that for some fields in some situations, the schema is wrong.

It happens for fields whose field type has a single optional property. I could reproduce the problem for both single-value fields and for multi-value fields.

Problem 1: an empty single-value field

Take a field type with only one property that is an optional string. Two things seem to happen when JSON:API builds the field's schema:

  1. Because the field item has one property, JSON:API flattens the item to that property. Because the property is optional, that schema allows null: {"oneOf": [{"type": "string"}, {"type": "null"}]}
  2. The field is optional, so FieldNormalizer::getNormalizationSchema() wraps the item schema in another oneOf with null, to allow an empty field.

The result has null in it twice:

{"oneOf": [{"oneOf": [{"type": "string"}, {"type": "null"}]}, {"type": "null"}]}

The validation fails with "Failed to match exactly one schema".

I expected the following result:

{"oneOf": [{"type": "string"}, {"type": "null"}]}

Problem 2: a multi-value field

For a field with more than one value, FieldNormalizer starts from a copy of the item schema and adds "type": "array" and "items" to it. If the item schema is a oneOf, the oneOf stays at the top of the array schema:

{"type": "array", "items": {...}, "oneOf": [{"type": "string"}, {"type": "null"}]}

This gives me the following error: "Array value found, but a string is required".

I expected the result to be:

{"oneOf": [{"type": "array", "items": {"oneOf": [{"type": "string"}, {"type": "null"}]}}, {"type": "null"}]}

It appears no core field type has a single optional property, so core's tests don't catch these problems. We should add tests for it.

To reproduce

  1. Create a field type with one property that is not required.
  2. Add an optional field of that type to an entity bundle, once with cardinality 1 and once with unlimited cardinality.
  3. Create an entity with both fields empty, and an entity with values.
  4. Validate its values in the JSON:API response against \Drupal::service('jsonapi.serializer')->getJsonSchema($entity->get($field_name), []).

Comments

dries created an issue. See original summary.

dries’s picture

I think we can learn from the fix in #3634887: JSON Schema gets a second null option when a #[JsonSchema] attribute allows null without a type, and possibly reuse the acceptsNull() that was introduced there, when it lands.