Drupal boasts a feature-rich API whereby content entities may contain attached data or references to other entities in fields. These are either "base" fields that are always present for a given entity type, or bundle fields which are used only on a subset of "bundles" for that entity. The storage configuration for a field is always the same for all instances of a particular entity type.

Drupal 8's release cycle saw the introduction of decoupled/API-first concepts on top of the core entity system, e.g. JSON:API and REST modules. In contrib, GraphQL is also popular, however it provides additional abstractions on top of the field API.

When working with Drupal entities over an API, it is very helpful to have a schema for the data structure of a particular entity. This allows clients to know, for instance, what acceptable values may be sent or received for the value and format properties of a formatted text field.

This issue's MR enhances the core typed data, field and serialization APIs to provide JSON Schema representations of a field's properties. These field-level schemas may then be used to generate comprehensive schemas for fielded entities, e.g. as OpenAPI specs. Currently we leave this level of schema generation to contrib, however it could make sense in the future to incorporate something like openapi_jsonapi into core. Currently, the jsonapi_openapi 4.x development branch depends on this MR and is a good window through which to review this issue's functionality.

This issue only covers content entities. Config entities have some support over the API, e.g. in JSON:API module, however schema discovery for them is very different given config entities are not fieldable in the same way, and their schemas would be derived from the config schema and validation APIs vs. typed data.

Some technical notes for review

While this issue/concept was originally blocked by an inability to cache the outcome of a "supports" query on a normalizer, that was fixed in #3252872: Use CacheableSupportsMethodInterface for performance improvement in normalizers thanks in large part to changes upstream in Symfony.

As it turns out, the original solution of a new interface and method to get a schema is not possible due to the fact the resolved normalizer may not be accessed directly from the serializer. A proposed change of Serializer::getNormalizer() was rejected upstream. The consensus alternate approach, which is probably more elegant anyway, is to use a new normalization "format" of json_schema to retrieve the schema, if supported.


Original Solution (originally authored by @gabesullice):

Add a SchematicNormalizerInterface with a ::getNormalizationSchema() method.

Initial thoughts on a method signature:

  • $type is a supported interface or class.
  • $format is the encoding format.
  • $refinements is a parameter bag of anything that is required to return a correct schema. For example, ResourceObjectNormalizer::getNormalizationSchema() would need $refinements->get('resource_type'). Best practice would be for refinements to be documented on the method and then asserted in the method. It's imperfect, but the best I can think of.

I think under this system, every normalizer would be required to return a complete schema. Meaning that the JsonApiDocumentTopLevelNormalizer would be responsible for returning a schema that included schema for any child resource object(s). Alternatively, we could allow normalizers to return placeholder objects and resolve them separately. That might end up as an over-engineered solution though.

Finally, I think that we would put this method on the Serializer service so that normalizers will not need to specifically know which child normalizer services will be applied.

Issue fork drupal-3031367

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

gabesullice created an issue. See original summary.

gabesullice’s picture

Issue summary: View changes
gabesullice’s picture

Issue summary: View changes
wim leers’s picture

I like the name "schematic", especially alongside "deterministic". Very clear.

wim leers’s picture

Status: Active » Postponed
gabesullice’s picture

Status: Postponed » Active

Active because: "For now, this issue should be considered to be in an experimentation/discovery phase."

gabesullice’s picture

Project: JSON:API » Drupal core
Version: 8.x-2.x-dev » 9.0.x-dev
Component: Code » jsonapi.module

Moving to Drupal core.

xjm’s picture

Version: 9.0.x-dev » 9.1.x-dev

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

Title: [PP-1] Introduce "schematic" normalizers » Introduce "schematic" normalizers
bradjones1’s picture

I generally understand the interface proposed, except for:

$format is obviously the encoding format.

Finally, I think that we would put this method on the Serializer service so that normalizers will not need to specifically know which child normalizer services will be applied.

I want to gut-check how this would actually work. Assuming SchematicNormalizerInterface extends NormalizerInterface, a module that wishes to generate a schema, e.g. OpenAPI, would do something like (pseudocode):

$json_schema_formatted_schema = $serializer->getNormalizationSchema(Thing::class, 'schema_json', ['bundle' => $bundleName]);
return decorateForOpenApi($json_schema_formatted_schema);

And, under the hood, the serializer would call Serializer::getNormalizer() in basically the same way as if you were normalizing the data?

Would all normalizers (basically, anything implementing NormalizerInterface other than the serializer itself) then need to set

protected $format = ['api_json', 'schema_json'];

...in order to say hey, pick me when you're doing normalization of either data or schema?

OR does $format in the ::getNormalizationSchema() mean the format the data would be normalized in? Thus we are saying, each serialization format is coupled by definition with a schema format? E.g., api_json gives you schema in json-schema?

bradjones1’s picture

Mulling this over a bit more, I think my confusion stems from the basic underlying question: What do we mean by "schema," as far as what format is it represented in? In the example of OpenAPI, it is a superset of JSON Schema, so if I start with a normalized array of a JSON schema, I may not have all the information I need to add the OpenAPI metadata, for instance.

Maybe I'm misunderstanding. Currently, Schemata module generates a Schema object which basically looks like a collection value object for typed data definitions. This is helpful, but then you need to basically normalize the Schema object itself, which doesn't seem to be the intent here. Or perhaps I'm way off base.

bbrala’s picture

I think schema should be defined as the definition of data. That should indeed enable other modules to take this definition and output that through a spec like json-schema or OpenApi.

The point made about 'deterministic' normalizers is that we need to know what normalizer we end up in. The fact we now define in core which normalizers do not change based on the data but are resolved by class and output format (the change in #3252872: Use CacheableSupportsMethodInterface for performance improvement in normalizers) means you should be able to determine the path the normalizers would take AND make sure the normalizer you get will always be the same.

*Thinking out loud here*
In the IS there is mention of using a parameter bag to determine some facts you cannot get from the class with bundle being the obvious example. I'm thinking though if the schema that can be provided uses the same routing as jsonapi is should be easy enough to determine which resource type we are talking about and going through the motions of generating the schema. My mind does wander towards endpoints with multiple resourcetypes though, this might end up being a pain, but something we should consider eventually.

Anyways, i agree a POC feels quite realistic at this point :)

bradjones1’s picture

Thanks for the reply.

I think schema should be defined as the definition of data. That should indeed enable other modules to take this definition and output that through a spec like json-schema or OpenApi.

Agreed, though that basically is a tautology - the schema is a definition of data, which is the definition of a schema. I might not have been entirely clear before, but I'm trying to get at - in what format? My example with OpenAPI is appropriate, I think, in so far as it's "like" json-schema but a bit extended. So it needs "more" raw information from which to pull.

I would need to do some research but I think the type for the return value of this method (if we're following the pattern of making this a new interface) needs to be determined first, and then everything else kind of flows pretty logically from there.

I'll need to do some more research with this in mind but I feel like json-schema might be one of, if not the only, options here for a well-known, interoperable schema definition that does not have Drupal reinventing the wheel or simply doing something like returning a bespoke value object.

bradjones1’s picture

From #contenta just now in Drupal Slack:

richgerdes @bradjones1 I wouldn't say that schemata was deprecated, but the jsonapi portion of openapi is definitely not being actively worked on. I also haven't had time to work on the schemata and openapi stuff in general, so its definitely a bit stale. I saw you opened some tickets and work working on some stuff, and I'm happy to help you move those forward. I personally don't think the jsonapi schema system fully replaces the need for openapi, since openapi makes the requests library agnostic, where the jsonapi schema system still requires the client to know that the platform is based on jsonapi and how the various endpoints work. I'll try and take a look at your issues soon if you want. I'd also be open to adding you as a maintainer if you are actively using openapi and schemata in a project, since it could definitely use some attention
bradjones1 Thanks - agreed on all points. I am actively using both on a project, so up to you if you'd like me to ping you when I have patches that I think are mature. I'm also happy to be a maintainer; @e0ipso has added me to some projects of his and I haven't screwed the pooch, and have been very active on simple_oauth lately and he hasn't regretted it, so up to you :wink:
bradjones1 There is a bit of duplicated code between them but I don't think that's a reason to like, make jsonapi_schema depend on schemata, but it looks like they might share some origins so it wouldn't be surprising if I keep finding some issues that apply to both.
bradjones1 I'd be interested your thoughts on how to be more DRY for both these modules. Perhaps a library they can both use for the json:api normalization?
richgerdes I think that the jsonapi_schema stuff should have been built on schemata in the first place, since the schema is all json schema. When Gabe first started on the implementation he didn't want to have extra unnecessary dependencies, and since schemata had support for both rest and jsonapi, it didn't make sense. Ideally, we should probably have created a "jsonschema" module, and made openapi and jsonapi depend on that, or at least moved the platform specific code to separate sub modules? Who knows? Hindsight is always clearer. I think its a little awkward to add the dependency now, but maybe the openapi side of the house could just work off of the jsonapi_schema system instead and schemata can be left for more general entity schema and rest? I don't want to duplicate efforts, but at the same time changing either system my complicate thingsAnyways, I've seen you as an active person in this channel in the relevant issues queues so I'm good making you a maintainer. I'm always around if you want need anything, or have questions, and happy to still be involved. Unfortunately the project isn't core to anything I'm working on at the moment.I've added you as a maintainer on the Openapi and openapi json modules. If you want to help with the rest or UI related components, just let me know. Unfortunately I don't have access to edit maintainers for schemata, so @e0ipso may need to help you out (or we'll need to get @grayside to give you access)
e0ipso Dropping Schemata was one of the main drivers behind splitting openapi in openapi_jsonapi and openapi_rest. If we restrict ourselves to openapi_jsonapi, I believe Schemata should not be used. Sadly, that is still not the case. But instead of adding more onto Schemata, I would invest that effort into moving away from it.
bradjones1 Thanks for the info. What do you feel about #3257327: Computed/read-only fields should not be marked required / my question about why the required test includes read-only properties? Also any thoughts on adding me to schemata to clean up a bit?
bradjones1 @richgerdes @e0ipso @wimleers (he/him) @gabesullice @mglaman I've opened #3258113: [META] Future of Schemata/Compatibility with other ecosystem modules to recap and hopefully move forward on this conversation around Schemata & other modules that pertain to schema.
bradjones1 Particularly if I could get feedback from this group on:Is moving away from Schemata for json:api matters basically the direction we're going?Is a module akin to https://www.drupal.org/project/json_schema_api the lighter-weight replacement that is focused on json-schema formatting, and not trying to do all the other things (HAL, REST, whatever)
e0ipso I added my thoughts in the issue.TL;DR there was a solid plan towards the end of the initiative. However the push for JSON:API in core drained us all so much that effort in anything else was halted. Still, I believe the plan holds well today.
bradjones1 Thank you :pray:
Björn Brala (bbrala) I've have up to a day a week to work on json:api. Personally, because getting interop right is quite important to make Drupal an even better choice for decoupled (and integration) scenario's I will be going down the thread that was mentioned by wimleers (#3252872: Use CacheableSupportsMethodInterface for performance improvement in normalizers#comment-14353328) in the coming time. It won't be extremely fast, but I should be able to keep things moving on that side at least. (edited)
bradjones1 @Björn Brala (bbrala) Thanks. I am actively working in this space, too. I think the key to unlocking schema generation is this issue, after a lot of reading today -> #3031367: Generate JSON schema for content entity types#comment-14365495
bradjones1 I'd appreciate your (or @gabesullice, since he wrote the IS) thoughts on my last comment about the interface. I think it could be relatively "easy" to get a POC written.
bradjones1 I'm still trying to get my mind wrapped around the data model but I think this means that we could get away from "normalizing" the data definition objects themselves to get schema, and instead ask the serializer to find the matching normalizer and get us the schema?
Björn Brala (bbrala) I think I can answer, but it's almost midnight and tomorrow is gonna be quite busy. Format in normalizers is the output format at least in the normal normalizers. But if that wasn't the full question I'll get back to it Friday:)
bradjones1 OK thanks... I think then I need to understand what everyone in core/the issue queue/etc. means by "schema" - is it in a particular format, itself? Or to my point in the issue,Thus we are saying, each serialization format is coupled by definition with a schema format? E.g., api_json gives you schema in json-schema?
Björn Brala (bbrala) posted :slightly_smiling_face:
Björn Brala (bbrala) That issue i commented on is hard man. Trying to wrap my head around how that would work.
bradjones1 Well, lest we get too stuck on the idea that Gabe suggested (though I think it's good)...
Björn Brala (bbrala) If I zoom out to the overarching goal I think its really important we work towards something that would support getting OAS support up to spec.
bradjones1 The main issue is, what is the intermediate schema representation?
Björn Brala (bbrala) Yeah
bradjones1 I did a little research - it seems as though OAS 3.1 is basically json-schema with some extra top-level metadata
bradjones1 Schemata/openapi right now isn't OAS 3, but if we said, OK, new major version, it's 3.1 - then we actually could get very close. And then things like jsonapi_schema which are concerned with only one resource type, well they just reveal the part they need in that format.
Björn Brala (bbrala) The reason i say oas for one is the fact that our goverment actually states in the api guidelines that oas3 should be supplied for all goverment api's.
bradjones1 https://apisyouwonthate.com/blog/openapi-v3-1-and-json-schema
Björn Brala (bbrala) That is actually awesome.
bradjones1 Or to put it another way, OAS 2/where we are at now with our rat's nest, means you can kinda go from OAS -> json-schema. But not back.
Björn Brala (bbrala) When i looked into it a while back, speccing jsonapi in oas2 is not even completly viable
bradjones1 It sounds like our opportunity, then, is to say, json-schema is the format of record for return value from "schematize-able" normalizers, and we should be able to then go json-schema -> OAS 3
Björn Brala (bbrala) a lot of not very specifc parts in there.
Björn Brala (bbrala) if that is possible that could be nice.
Björn Brala (bbrala) I was also thinking today, perhaps we should just leverage typed data?
bradjones1 I think that is more or less a given, because typed data is the data source. (That is, unless you consider how to handle things like jsonapi_extras' field enhancers/overriding, but we can set that aside for a second.)
Björn Brala (bbrala) But I must confess i haven't done much with that part of core
bradjones1 So then the question is, do we in the course of introducing this need to also build some sort of typed data -> json-schema mapper thingie
bradjones1 But honestly I think a lot of that is like, already in the existing code
Björn Brala (bbrala) If json-schema wont hold us back then that seems like a good idea (TM)
bradjones1 B/c typed data again, is the data source. So I think that question is actually more or less already solved
Björn Brala (bbrala) i was triggered because you said in the issue we will miss information (edited)
bradjones1 B/c what else do you have to go on currently when looking at a entity or field, but the typed data definition
Björn Brala (bbrala) yeah
bradjones1 I think that was my OAS 2 experience speaking
Björn Brala (bbrala) hehe
Björn Brala (bbrala) OAS2 and jsonapi was no fun unfortunately.
bradjones1 So the pseudocode method signature would be something likegetNormalizationSchema(TypedDataInterface $thing, string $normalizationFormat, ParameterBag $wellDefinedParameters): array
bradjones1 Or maybe it could return some sort of json-schema value object that supports __toArray() but like, probably overkill unless there is an existing php implementation of a json-schema DTO
Björn Brala (bbrala) I just thought of something that might be inspiring, let me search.
bradjones1 It would be interesting, if there is a DTO model to be used here, that like Gabe was saying, some of them could be placeholder-able or like, override-able when they get normalized, which could be a hook point for extras
Björn Brala (bbrala) I worked a little on an OAS schema generator for this package https://github.com/laravel-json-api/laravel
Björn Brala (bbrala) let me find it
Björn Brala (bbrala) Yeah that would make extensibility a lot easier.
bradjones1 So yeah, it could return some sort of value object... and if you like, normalized it directly, you'd get json-schema.
Björn Brala (bbrala) Ah it was this: https://github.com/byte-it/openapi-spec-generator and was thinking of this (goldspecdigital/oooas)  depoendency. Not too sure if that would help. Bit tired hehe
bradjones1 But you could also have like, a schema DTO normalizer that supported a format for, say, OAS 3 which would add the extra top-level stuff on to it and make it OAS.
bradjones1 But perhaps that's overengineered. I think the main question is a variation of my original query - what's the return value of that method
Björn Brala (bbrala) json-schema sounds like a plan tbh
bradjones1 https://github.com/goldspecdigital/oooas looks kinda like the DTO I'm envisioning but like, bespoke
Björn Brala (bbrala) But dunno how to put that in something sane right now.
bradjones1 Tell me more about what's fuzzy to you
bradjones1 (Not that I have a very clear picture myself, but perhaps your blind spots are different than mine)
Björn Brala (bbrala) Currently my head is mostly fuzzy tbh. Sick baby which made me sleep 5 hours last night :x
bradjones1 LOL I was going to ask you why you're even up working
bradjones1 But perhaps this is distraction while watching the baby. I'm sorry you're dealing with that... joys of fatherhood
Björn Brala (bbrala) :stuck_out_tongue:
Björn Brala (bbrala) Its fine, I like to at least to everyone who is  big help in real time sometimes, this was an oppertunity ^^
Björn Brala (bbrala) I'm not sure I would like to build out a whole set of value object for json-schema if we can help it.
bradjones1 Agreed on the last point (and thank you for making the effort to connect, it means a lot) - though I wonder how to handle overrides
bradjones1 The json:api normalizers can't be decorated because of the very strict namespace-checking... that would be the best way to handle it.
Björn Brala (bbrala) (also typing some free thoughts). We will need to have objects for the different levels. Just wondering if there is something stable we might be able to use. Although dependencies into core is hard.
bradjones1 I guess extras could continue its pattern of abusing that with imposters
Björn Brala (bbrala) Nah, i'd make it pluggable in some other way. Events are working out pretty fine tbh
bradjones1 So in that case, can the normalizers in json:api core have added events to fire off which extras could hook on to? And that's... it?
Björn Brala (bbrala) There might be performance considerations :wink:
bradjones1 B/c right now we are having to generate schema by loading the datatype/field definition entities directly, and then normalizing them into like, json-schema or whatever. It seems like we can use the existing set of normalizers that ships in json:api core, and make them implement this new interface.
bradjones1 Schema should be pretty stable and easy to cache/invalidate, no? The tags would basically be entity type/field/bundle/whatever definitions?
Björn Brala (bbrala) Yeah
bradjones1 And we already have CacheableNormalizationInterface
bradjones1 which could be the return type instead of a straight up array
Björn Brala (bbrala) I don't have the whole discussion on the (ab)use of the serializers really available in my head right now. But there were some good reasons the normlizers have been locked down :wink:
Björn Brala (bbrala) Symfony wont allow returns of something else than basic types
bradjones1 I'd imagine if for nothing other than trying to avoid a flood of support issues for invalid data
Björn Brala (bbrala) and arrayobject (which has the goal of representing {} )
Björn Brala (bbrala) Hmm
bradjones1 I'm not sure that's 100% true in the case of Drupal though - b/c https://git.drupalcode.org/project/drupal/-/blob/9.4.x/core/modules/json...
bradjones1 that's clearly returning a cacheablenormalization
bradjones1 I was scratching my head at that today b/c of a link Wim linked from symfony about a similar question
bradjones1 Or maybe that's what you meant - you have to lock them down b/c we do this. Ah, yes.
bradjones1 Then we agree
Björn Brala (bbrala) Yeah, and the issue is, we were not abiding to the interface, which breaks in symfony 6, so our cacheablenormalization now extends arrayobject not to break xD (edited)
bradjones1 Yeah sorry now I'm the slow one
Björn Brala (bbrala) no worries
bradjones1 Well I mean, if we're sinning, might as well keep sinning?
Björn Brala (bbrala) hahaha
bradjones1 It's also true that this schema DTO thing we're talking about doesn't really get "normalized" in the same sense
Björn Brala (bbrala) there is more discrepancies
Björn Brala (bbrala) We wont have instances of objects at all levels i think?
Björn Brala (bbrala) nvm
bradjones1 In this pseudocode DTO I'm talking about?
Björn Brala (bbrala) we have a type and then field instances
Björn Brala (bbrala) ehhh :stuck_out_tongue:
Björn Brala (bbrala) The dto could be an array though.
Björn Brala (bbrala) Even though that sucks in a few ways.
bradjones1 Well then it's just an array
bradjones1 Not really a DTO
Björn Brala (bbrala) yeah
Björn Brala (bbrala) I wish symfony reacted a little more positive in allowing dto as return type ;(
bradjones1 I think it can return an array, that's fine - but we would just need to give modules an opportunity to alter the schema at some point before it gets flattened
bradjones1 So really to answer the question about altering: we actually need two new events. One for the schema, and another for the normalization itself.
bradjones1 That way jsonapi_extras can stop providing its own normalizers, and simply act on the events
Björn Brala (bbrala) i keep thinking, why would we bolt on something new when symfony would just allow a new normlizer which could do exactly that
Björn Brala (bbrala) I'm starting to think in circles T_T haha
bradjones1 I think you're overthinking it. The question of allowing an object return type for normalizers doesn't really block us here
Björn Brala (bbrala) Ah this issue: https://github.com/symfony/symfony/pull/43498
Björn Brala (bbrala) We fixed it with the arrayobject, and i got burned out on pushing on this :stuck_out_tongue:
bradjones1 Yeah I don't think it's necessary here though
bradjones1 I think an event on the normalizers to both 1) allow altering the schema, when it's generated and 2) allow altering the normalization would be fine
bradjones1 B/c you then have a single normalizer which gets resolved for the data, if you're either normalizing actual data or resolving a schema.
Björn Brala (bbrala) Sounds reasonable.
Björn Brala (bbrala) :slightly_smiling_face:
Björn Brala (bbrala) Wish there was a "insert slack discussion into issue"  button lol
bradjones1 I'll make a link. I think they finally got Drupal Slack into like a freebie-paid plan so the history doesn't go away
Björn Brala (bbrala) :smile:
Björn Brala (bbrala) I tent to paste the discussion itself
Björn Brala (bbrala) Since logs WILL be gone at some point in the future
bradjones1 I don't love events for altering the normalization (b/c in theory you should just provide a new normalizer) but I think it helps with encapsulation - you could have a single event subscriber that alters the data AND the schema.
Björn Brala (bbrala) I would need to get a feel for that inside an editor tbh :stuck_out_tongue:
Björn Brala (bbrala) I feel like we made some prgress here, at least the mental map of this is a lot clearer in my head now :x
bradjones1 AND AND this would resolve #3226185: Refactor field enhancer schema generation b/c you would have access to the original schema during altering
Björn Brala (bbrala) Hehehe :stuck_out_tongue:
bradjones1 OK I will summarize this on to the issue. I think if I approach this from the perspective that openapi_jsonapi et. al. would be more or less rewritten to support this new approach, the parts that need to live in core would be the new interface, and (this is the stickiest part) a default implementation of json-schema generation from typed data
bradjones1 However much of that can be lifted from contrib I think.
Björn Brala (bbrala) Yeah it might need some massaging to get up and running :wink:
bradjones1 Though with @mglaman’s awesome recent work on demystifying typed data, less scary than it would have been to me before.
Björn Brala (bbrala) yeah, that triggered me also lol
bradjones1 Awesome, I feel like this really helped. I really really appreciate your time Bjorn. Are you going to DrupalCon NA?
bradjones1’s picture

So per the late night brainstorming session with @bbrala pasted above:

  • json-schema seems to be the de-facto standard to generate schemas; this is a mature, well-supported and portable format, and there really isn't any modern alternative.
  • Earlier attempts at providing openapi schemas were limited by OpenAPI 2.x/Swagger's not-perfect intersection with the json-schema spec. That has since changed in OAI 3.1, and so the use case of generating an openapi spec for the json:api API is clear-cut. You can even mix and match schema styles if need be (but I don't think we'd need to.)
  • Currently, modules like jsonapi_schema and openapi_jsonapi need to do normalization of the data type definitions; the idea here is that we would get schema from fields' respective classes (basically, the things that actually get normalized into json) instead of having to normalize the data type definition. There is a known universe of normalizers shipped with json:api, and they are @internal, so we can encapsulate this logic, there.

A bit more of an archaeological dive:

json:api had integrated schema generation at one point, which was removed in this commit, but it provides a good example of a more tightly integrated approach that was pulled out, and seeded Schemata.

bradjones1’s picture

This is all very content-entity centric thus far of course. We also need to think about config entities, although let's face it, most of the use case here is for content. So I think it could be appropriate for the config entities to be perhaps less robust. See #2994473-10: [META] JSON API's normalizers support schema tracking, to guarantee comprehensive schema

nod_’s picture

Very glad to see the movement here. This would help tremendously :)

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

andypost’s picture

Issue summary: View changes
Issue tags: +Needs issue summary update

The blocker is closed as outdated so IS needs update

bbrala’s picture

Think this is the issue, we can use the symfony property for that I think.

#3252872: Use CacheableSupportsMethodInterface for performance improvement in normalizers

bradjones1’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

Updating IS.

wim leers’s picture

👀👀👀👀

bradjones1’s picture

Component: jsonapi.module » serialization.module
Status: Active » Needs review
Issue tags: +Needs subsystem maintainer review

Changing the component to serialization because this is mostly not specific to JSON:API.

I am marking NR because I'm deep enough into this now that I would really like some maintainer/committer review to ensure this isn't totally off-base. I'm personally pretty proud of how this is implemented with the interface and the mostly-automatic integration of the base normalizer with the new Attribute.

bradjones1’s picture

So, yeah, this now requires a change to symfony/serializer which is a bummer, and I'm not quite sure what the work-around would be. I've chosen to just include the patch for now, in hopes that as we work on this that either 1) Symfony will make a minor change to two methods from private to protected, or 2) someone smarter than I comes up with a different alternative.

The issue is that we are adding a new method to normalizers to express information about their normalization, but the selection of said normalizer is hidden behind the serializer's ::normalize() method, where it finds the correct normalizer from all those registered. We need to be able to essentially statically-analyze the selected normalizer. We need to peer into ::getNormalizer(), which is currently private.

There is a dirty hack to invoke private methods with reflection, but I doubt that would pass a core quality gate and makes me feel very dirty.

Still leaving NR because I would love feedback on this and the overall approach. Not letting this slow down development, but it is a sticking point.

bradjones1’s picture

This should now be to the point of producing a spec-compliant schema for a resource object. There's plenty more work to be done, but I'm pretty happy with how relatively easy it was to implement the earlier generic work on schematic normalizers to JSON:API in basically an afternoon.

Re: our need for the Symfony Serializer to change two methods from private to protected, I've received some initial feedback from two Symfony maintainers and both asked good questions for follow-up.

The concrete bad news is that Symfony 6.4 and 7.0 are in feature-freeze, so the earliest this change would be accepted is 7.1.

Parallels were drawn between what we're doing and api-platform, which is an official-ish reference implementation of Symfony for doing fancy API things. They are generating JSON Schema using object reflection, and I laid out how this isn't much of a parallel to the Drupal entity and field APIs. We'll see if anyone's convinced by my reasoning enough to change the method visibility.

So optimistically, this would be blocked on Symfony 7. If this is a non-starter, there are two other options:

  1. Most radical: Ditch the Symfony serializer/normalizer because we barely use it now and abuse it when we do. This would be a major lift/BC break, but it would also alleviate a lot of headaches encountered in the D8+ lifecycle.
  2. Fork the Symfony Serializer and implement our own which implements SerializerInterface. This increases maintenance overhead and we are forked further from Symfony core, which we're trying to avoid. But there is precedence, e.g. we have our own YamlLoader.
  3. (Ab)use NormalizerInterface::normalize()'s context parameter and allow normalizers to return a value object containing a schema. There is some precedence for returning a value object as JSON:API module needs cacheability metadata and so it passes around CacheableNormalizations. This is a bit of an escape hatch if we get cornered by other options. The downside is that is makes ::normalize() even more polymorphic and magical and also implies the value you pass is "real data," instead of potentially being "just" a supported class or interface name, which is all we might have during schema generation. Then again, that parameter is already mixed and so we could just define by convention that if the special context flag is passed, the value is to be regarded as the same as to ::supportsNormalization(). We could also use this approach to start and then deprecate it in favor of a more explicit method if we can ever make it publicly callable.
bbrala’s picture

First off, im so happy to see you working on this issue. :)

I've been tring to wrap my head around this and am having a hard time hehe.

One of the things we might be able to do is perhaps use the serializer to keep track? We already overwrite the contructor for that and pass that up to the parent. Perhaps there is a way to add the required data to the normalizers there using our own interface. This also feels very tacky in a way, but could be a place where we can track that information and allow it to be pulled in the normalizer itself perhaps.

This is purely based on looking at code, have not tried and see if that would work and how that would look.

The suggestion to use another format for the json_schema in the github issue and go from there is also interesting, wouldn't that work? it might not be the fastest of things to serialize again and go through all the paces to get the schema and would add another whole normalization pipeline, but it could be cached pretty well I guess. This does seem to combine pretty well with the 'describeby' member that was added in jsonapi 1.1 though, which is a link. It could then be a link like '/jsonapi/node/article?format=json_schema'.

Basically, i have no real awnser here right now. I'm not convinced we will see Symfony change the visibility right now. Hopefully my thoughts are helpfull in a way.

bradjones1’s picture

Status: Needs review » Needs work

Thanks @bbrala for the feedback. Sorry I couldn't make it to Lille this year to work on this in person! We got a lot done last time we pair programmed in Portland.

One of the things we might be able to do is perhaps use the serializer to keep track? We already overwrite the contructor for that and pass that up to the parent. Perhaps there is a way to add the required data to the normalizers there using our own interface. This also feels very tacky in a way, but could be a place where we can track that information and allow it to be pulled in the normalizer itself perhaps.

This is purely based on looking at code, have not tried and see if that would work and how that would look.

I think I follow what you're saying, and in theory yes we could do a lot of shenanigans within the @internal portion of JSON:API module. However there are two main gotchas to this approach: 1) despite these methods not being "technically" extend-able, modules like Extras violate that with imposters and so any normalizers from modules like that, while violating the internal rule, would need to also change. And 2) this would only address JSON:API's normalizers, but not those in the core typed data API, which provides all the schema for properties.

The suggestion to use another format for the json_schema in the github issue and go from there is also interesting, wouldn't that work?

I ran into your feedback while coming here to say that yes, I think this suggestion for treating JSON Schema as another "format" is going to be the path forward.

...it might not be the fastest of things to serialize again and go through all the paces to get the schema and would add another whole normalization pipeline, but it could be cached pretty well I guess.

Unless we're talking about different things here, and I don't think we are, I don't see a performance hit. The approach with ::getNormalizer() would depend on the same normalizer resolving process that was optimized recently with the help of upstream changes, and this is just another path for resolving a normalizer but for a different $format. Also as it stands I don't believe that performance should be a huge issue here, as the resulting schemas could be cached and won't vary much.

This does seem to combine pretty well with the 'describeby' member that was added in jsonapi 1.1 though, which is a link. It could then be a link like '/jsonapi/node/article?format=json_schema'.

That's an angle I hadn't explored yet, however I think it's really powerful and would open the door to a very low-code way for us to bring the functionality that currently lives in jsonapi_schema module into core. I almost wonder if that shouldn't be the near-term goal vs. the OpenAPI integration, though OAI is basically just formatting the same data in a slightly different way, so these are not in competition.

Basically, i have no real awnser here right now. I'm not convinced we will see Symfony change the visibility right now. Hopefully my thoughts are helpfull in a way.

Yeah, after the third maintainer basically said "no," I agree, and honestly this is how the process should work. I iterated on the initial idea (an interface for the normalizer), it turns out that won't work and is blocked on upstream cooperation anyway, but we land on a solution that might even be "better."

I am toying with different ways of implementing this, however, that would still perhaps leverage an interface and/or trait to do most of the heavy lifting for the normalizers. I have some ideas that I'll toy around with in the MR as I refactor out of this solution and into the alternative for $format.

Marking NW as I only really had it in NR to solicit this kind of help.

bradjones1’s picture

Issue summary: View changes
bradjones1’s picture

Status: Needs work » Needs review

Putting this back to NR as I would still love feedback from maintainers and committers.

The MR now contains a refactor of the initial approach, which I think overall is an improvement. TL;DR, you now request schema from the normalization system by specifying the $format as json_schema.

The only real nit I can see with this approach is that normalizers are resolved based on the data to be normalized and the format, and so there is theoretically a different universe of normalizers selected for schema vs. a specific format. That's pretty much unavoidable with this approach. In theory we could get 100% the same normalizers by specifying the format same as the eventual normalization and hinting in $context that we actually want the schema. (This is in spirit what we wanted by having access to ::getNormalizer().)

I don't love this but don't hate it, in theory. The practical reason we can't take this approach is that normalizers which don't care about the $format (which is actually the majority of them) need to be aware of that context, and might return a "normal" normalization instead of schema. We have to address this as it is with this change to NormalizerBase::checkFormat() to special-case the json_schema format as an exception to the "if I don't specify any formats, I serve them all" default.

Another question would be if the JSON:API implementation should wrap schema in CacheableNormalizations or not.

bradjones1’s picture

One thought about type-safety on the returned value (since we are using ::normalize() for schema as well as normalizations) could be to require it to specify the JSON Schema meta-schema it implements in $schema, which would be a quick check for the calling code to say "this is for sure a schema." I'm not sure if that's necessary, but one idea to throw out there if people are worried about non-core normalizers somehow getting tricked into returning a normalization when we really want a schema.

smustgrave’s picture

bradjones1’s picture

Title: Introduce "schematic" normalizers » Generate JSON schema for entity types

Making title less technical and more accurate as to the current goals.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

bradjones1’s picture

Status: Needs work » Needs review

Good bot.

bradjones1’s picture

bradjones1’s picture

Title: Generate JSON schema for entity types » Generate JSON schema for content entity types

Config schema generation is very different and a path forward for them is not very clear yet. The recent work on validation for config entities will help unlock this in the future, however.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

gábor hojtsy’s picture

Is there a high level summary for this issue? I can't tell from the huge changeset what exactly is being proposed here. What's the before/after? "Content entities were serialized in BLOBs before (in which cases?) but now they are serialized in JSON?" What's the benefit? Which APIs are affected, etc? Especially of an issue of this magnitude I think it would be important to outline these. It should help with reviews as well :)

wim leers’s picture

Issue tags: +Needs change record

+1 to what @Gábor Hojtsy said. Change records would be really helpful to understand this functionality too. 😇

bradjones1’s picture

Issue summary: View changes
bradjones1’s picture

Status: Needs work » Needs review

Draft CR added. IS updated. MR rebased. Back to NR.

bradjones1’s picture

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

bradjones1’s picture

Status: Needs work » Needs review

Conflict was from the conversion of typed data plugins from annotations to attributes 💯

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

bradjones1’s picture

Status: Needs work » Needs review

Rebased. Back to NR.

bradjones1’s picture

bbrala’s picture

Issue tags: -Needs change record

I went through the MR and have some comments. All and all it is a good implementation of what we talked about (quite) a while back.

Change record is available.
Issue summary seems up to date, although format is a little more freeform, would prefer to move to default setup. So keeping that tag.

bbrala’s picture

Status: Needs review » Needs work
bbrala’s picture

Some small questions for now.

bradjones1’s picture

Related note, so close to having a proper JSON:API 1.0 and 1.1 schema available on jsonapi.org but it's blocked on infra: https://github.com/json-api/json-api/issues/1749

That doesn't mean we have to block this issue on that, and now that it's in a const it's easy enough to update.

bradjones1’s picture

Status: Needs work » Needs review
bbrala’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs issue summary update

I've gone through the changes and all previous comments. Think we got through everything and the feedback has been adressed. Remove NISU since the wording has changed referencing the other issue.

I'm gonna go out on a limb and push to RTBC. <3

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I've added a couple of small comments to the MR that could be addressed.

bradjones1’s picture

Status: Needs work » Needs review

I have addressed your comments.

bradjones1’s picture

A bit of a fat-fingered rebase but I am hoping that this is very close to RTBC still.

bbrala’s picture

Status: Needs review » Reviewed & tested by the community

The comments by alex have been adressed. I agree with the argument for the array argument. Back to RTBC

bbrala credited larowlan.

bbrala’s picture

Updated credits

needs-review-queue-bot’s picture

Status: Reviewed & tested by the community » Needs work
StatusFileSize
new33.47 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

bradjones1’s picture

Status: Needs work » Reviewed & tested by the community

Putting this back to RTBC as it only needed a phpstan-related update relating to improved analysis as this awaits commit.

bradjones1’s picture

Updating tags. Marking this as a contrib blocker because new versions of OpenAPI and OpenAPI JSON:API depend on this.

kopeboy’s picture

Issue summary: View changes

Thank you!

jsacksick’s picture

I think the patch needs a reroll as it doesn't apply to 11.0.6.

bradjones1’s picture

I've rebased against 11.x, if it doesn't apply to a specific tag it would be because of a change in HEAD.

larowlan’s picture

needs-review-queue-bot’s picture

Status: Reviewed & tested by the community » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

bradjones1’s picture

This is likely due to #3100732: Allow specifying metadata on JSON:API objects going in which is a good thing. Needs a rebase then should be back to RTBC.

bbrala’s picture

Assigned: Unassigned » bbrala
bbrala’s picture

Status: Needs work » Reviewed & tested by the community

Rebasing took a little effort because we need to deprecate some extra arguments. I also had to undo contructor promotion since the merged json:api metadata issue changed the contructor parameter.

I think the changes are small enough no to warrent a new review, all is green still.

bbrala’s picture

Assigned: bbrala » Unassigned
larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Some questions on the MR, will keep an eye out for when this is addressed, as getting this into 11.2 early is a priority

bradjones1’s picture

Status: Needs work » Needs review

Back to NR.

bbrala’s picture

Status: Needs review » Reviewed & tested by the community

All things have been adressed.

Exciting times :)

  • larowlan committed 8e29ee2b on 11.x
    Issue #3031367 by bradjones1, bbrala, gabesullice, wim leers, larowlan,...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -11.2.0 release priority +11.2.0 release highlights

Committed to 11.x - thanks

We've got ~6 months to find any issues downstream from here - likely started with JSON API extras which is maintained by several of the people in this issue.

Great work all

bradjones1’s picture

Is this eligible to go into D10 LTS?

larowlan’s picture

Under https://www.drupal.org/about/core/policies/core-change-policies/allowed-... - I don't think so.

Published the change record.

wim leers’s picture

Status: Fixed » Patch (to be ported)
Related issues:

🤯 What a christmas present! 🤩

Questions:

  • The change record doesn't mention it, but the commit does change the JSON:API version: JsonApiSpec::SUPPORTED_SPECIFICATION_VERSION = '1.1' is now a fact. So #3305324 is partially done? Completely? See #3305324-18: [Meta] JSON:API 1.1 spec compliance/support. It seems that meta indicates there's a lot more to be done to be 1.1-compliant, so it's a bit surprising to see that constant being changed. I think either an updated change record that clarifies it, or an additional change record would be appropriate?
  • Next up: #3426508, and I posted some pointers at #3426508-4: Generate JSON Schema for config entity types.
  • This seems to statically define JSON schemas, which is not good enough: many field types have settings that impact what the correct JSON schema would be. IOW: the JSON schemas have to be dynamically defined for them to be precise. For example: min/max for integers and numbers, a limited set of valid values (enum in JSON schema), many strings have a particular format (https://json-schema.org/understanding-json-schema/reference/string#built...), and so on. Are there plans/discussions for how to support those more precise JSON schema descriptions? Perhaps this needs a follow-up?

Either way: HUUUGE leap forward! 🤩👏


EDIT:

Can we use this to our advantage in https://www.drupal.org/project/experience_builder? This seems to have the potential to simplify some of the shape matching functionality in XB, which literally is matching Entity field properties’ Typed Data Definitions (including validation constraints) against the JSON schemas of SDC props:

P.S.: first <hr> of the year 😁

bradjones1’s picture

Thanks, Wim!

I will reply to your specific points soon, but why is this now marked as to-be-ported? The reply above indicates this can't go into 10.x, so I think there's nothing to backport?

(To be fair, my main project in production is "still" D10 and this applies relatively cleanly, but I also understand if it can't go officially into D10 because of policy.) I would love for this to be supported in D10 as that would personally benefit me and make the new majors of OpenAPI/OpenAPI JSON:API D10 compatible but appreciate if that's just too much.

larowlan’s picture

Status: Patch (to be ported) » Needs work
Issue tags: +Needs followup

I think NW to add a follow up for at least the third of Wim's points is an appropriate status - tagging as such.

Getting this early into 11.2 allows us to smooth out any pain points so it would be great to see what the current approach is missing for XB and refine this before 11.2 comes out

bradjones1’s picture

Re: ##8 - thanks Wim for your very kind words.

The change record doesn't mention it, but the commit does change the JSON:API version: JsonApiSpec::SUPPORTED_SPECIFICATION_VERSION = '1.1' is now a fact. So #3305324: [Meta] JSON:API 1.1 spec compliance/support is partially done? Completely? See #3305324-18: [Meta] JSON:API 1.1 spec compliance/support. It seems that meta indicates there's a lot more to be done to be 1.1-compliant, so it's a bit surprising to see that constant being changed. I think either an updated change record that clarifies it, or an additional change record would be appropriate?

I bumped the version in this MR for a few reasons. One, our testing and validation features (e.g., that validates responses when assertions are enabled) depend on a schema for JSON:API itself, which is not exactly stable but the upstream maintenance of JSON:API overall is glacially slow. The schema file we had for 1.0 was draft and not that valid and so the updated 1.1 draft is much closer to truly representing the spec's requirements.

After reviewing the linked issue I would say that we very nearly support 1.1 at a basic level, though we "should" also finish #2955020: Spec Compliance: JSON API's profile/extention (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated to convey our profile.

TL;DR, it's time to bump it because we were far ahead of 1.0 with implementing features that would later go into 1.1. We don't strictly validate, but it's much closer to the current spec than we've ever been.

This seems to statically define JSON schemas, which is not good enough: many field types have settings that impact what the correct JSON schema would be. IOW: the JSON schemas have to be dynamically defined for them to be precise. For example: min/max for integers and numbers, a limited set of valid values (enum in JSON schema), many strings have a particular format (https://json-schema.org/understanding-json-schema/reference/string#built...), and so on. Are there plans/discussions for how to support those more precise JSON schema descriptions? Perhaps this needs a follow-up?

Schemas _may_ be defined statically, but they need not be. In addition, JSON Schema generation has no caching layer (the assumption being that the caller is responsible for this, e.g. OpenAPI module, and invalidation is based on entity definitions or whatever) so the entire schema for the normalization you are introspecting is generated on-demand. It is true that included in the merged MR was a trait and attributes that help provide static schemas... but that's not a requirement. This allowed us to add a lot of default schemas for primitive types and make it easy to define them this way, but you can also return any valid schema generated any way you like. So I think a follow-up for option module to provide a ListStringItemNormalizer with its own dynamic logic would be amazing. But it requires no change to the underlying API. Perhaps we could introduce some additional traits or whatever to assist with code re-use, but yeah.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.