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:
- 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"}]} - The field is optional, so
FieldNormalizer::getNormalizationSchema()wraps the item schema in anotheroneOfwith 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
- Create a field type with one property that is not required.
- Add an optional field of that type to an entity bundle, once with cardinality 1 and once with unlimited cardinality.
- Create an entity with both fields empty, and an entity with values.
- Validate its values in the JSON:API response against
\Drupal::service('jsonapi.serializer')->getJsonSchema($entity->get($field_name), []).
Comments
Comment #2
dries commentedI 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.