-
Notifications
You must be signed in to change notification settings - Fork 9.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate discriminator? #2143
Comments
FYI-- the JSON Schema Lint link is incorrect or isn't working https://github.com/OAI/OpenAPI-Specification/issues/jsonschemalint.com |
Thanks, @mewalig. Fixed. |
Are there any simple examples of how "discriminator" in OpenAPI would compare to the analogous schema as described using JSON Schema? Also, do any validators exist today that will properly enforce either the OpenAPI discriminator or the JSON Schema discriminator equivalent? I understand that this repo is not intended for tools, but in evaluating whether, out of two standards, one should be ditched, it would be nice to be able to play around a bit more with each in order to form a better opinion |
If you take my example schema from #2141 and remove the
Any compliant JSON Schema validator should be able to enforce a JSON Schema like the one I posted. I don't know about tools that enforce OpenAPI discriminator, but it sounds like you have found some already. You might want to try them again with the schema I provided (modified back to your original |
Yes, absolutely, please get rid of discriminator. My reasons explained in more detail at #2141 |
@tedepstein thanks for filing this! @mewalig a possible way to solve this problem based on adding some extension keywords to the most recent draft of JSON Schema might look like the following. First, let's just look at how to make inheritance work better at all: {
"oneOf": [
{
"$ref": "#/$defs/child1"
},
{
"$ref": "#/$defs/child2"
},
{
"$ref": "#/$defs/child3"
}
],
"unevaluatedProperties": false,
"$defs": {
"base": {
"className": "TheBaseClass",
"type": "object",
...
},
"child1": {
"$ref": "#/$defs/base",
"classRelation": "is-a",
"className": "Foo",
...
},
"child2": {
"$ref": "#/$defs/base",
"classRelation": "is-a",
"className": "Bar",
...
},
"child3": {
"$ref": "#/$defs/base",
"classRelation": "is-a",
"className": "SomethingElse",
...
}
}
} Here we've introduced two new keywords,
Code GenerationWhen a code generator looks at this, it is going to statically analyze the schema without an instance. It will ignore the ValidationWhen you validate an instance against this schema, the
Note that the "from" pointers are showing the dynamic scope, the path traversed at runtime including references. Note also that the names "base", "child1", "child2", etc. from under InstantiationIf we're trying to instantiate a class from a JSON instance, first we validate it and get the above annotation outputs. We notice that there are two So we immediately know that this JSON instance data is a valid "Bar", and that a "Bar" "is-a" ... If we want to make sure the other We notice that there aren't any other OptimizationTechnically, this is all we need. Whether you look at this schema statically (as a code generator would) or dynamically (as an instantiator would), you can figure out everything you need from your task. And none of it gets in the way of validation (unknown keywords are ignored by the validator). One of the reasons OAS has But you could definitely have one. I started trying to write it out, but it gets pretty annoying because either you end up with magical behavior (the way the value of the field suddenly has to match some other thing in the schema structure, and has to validate, which means the same value has to appear in two different places and be kept in sync), or it provides minimal performance benefits because you still have to do some validation. I'd want to understand what needs to be optimized in this case before throwing in more keywords. |
Thanks for the thoughtful response.
Just to clarify, my suggestion would not be to add any keywords, but rather, to remove them-- in particular, to remove "discriminator" and everything associated with it. Using the schema provided by @tedepstein, I was able to do everything I wanted in OpenAPI/JSON Schema-- without the discriminator keyword-- and it worked better without That said, I'm not familiar enough with the issues to know that there isn't some other use case that |
Yes, my example would remove
Just to be clear, we're talking about this example schema? #2141 (comment) Doesn't it use |
@handrews , that is a switch-hitting schema that includes
@mewalig couldn't get any available OpenAPI validator to work with If you remove
At first glance, the pattern you've described seems to address both of these. |
fwiw, personally, if a code generator works fine with a |
@mewalig , I don't think anyone here is making an argument to keep Also, the earliest release in which we could deprecate |
It says something (I'm not sure what, but something) that I totally forgot about this behavior, which is in fact my biggest objection to the keyword! 🤣 |
@tedepstein We merged a deprecation of
|
At the very least, I think it would be immensely helpful for the documentation (e.g. the spec and related example) to:
It might also be worth mentioning that #2 relies on fewer specification features, which are common to JSON Schema, and as such are better supported by validation (and other?) tools. Those changes alone would have saved me more than 10 hours of time going in circles with |
I still see Unions as currently supported by OpenAPI (through |
@sm-Fifteen , thanks for articulating this:
That's the benefit of Without these clear markers, pattern recognition is much more complex.
The case for deprecating |
Since a functional JSON equivalent to If that's the goal, then another possibility might be to have a "type" value of
and the corresponding JSON like this:
or
It seems to me that this would be easy to convert to equivalent JSON Schema, which would allow for easy validation of the OpenAPI schema, because the validator could just convert to JSON Schema and then validate that. The same would be true for code generators. In addition, there would be a lot of benefits if it allows OpenAPI to reuse familiar, mature and proven IDL constructs and/or syntax (lower barriers to adoption, higher functional value, less need for revision / change, etc) |
Clarity of intent. Standardized, unambiguous way to denote tagged unions, rather than relying on a loosely defined convention. "Convenience" translates into availability of code generators, validators and other tools: more of them, better consistency, and higher quality. All good things for the OpenAPI ecosystem and user community. These are the reasons described in recent posts here. I'm not arguing that it's ideal to keep |
Those are exactly the reasons that, at least imho, a
Any idea if that is currently being contemplated for JSON Schema? I'd definitely be interested to learn more about that.
fwiw, personally I would think this is a very low bar. In searching for more examples, I found a lot of posts asking unanswered questions about how to use discriminator, and my impression was that there doesn't seem to be much as far as documentation or complete examples, other than maybe one simple example which might be hard to translate into real-world use cases. |
@mewalig we (the JSON Schema project) have enabled extensible vocabularies in the new draft. We are hoping that OpenAPI folks will take the lead on code generation proposals. Not necessarily the OpenAPI project itself (although I'd be happy with that), but the OpenAPI community. OAS is the primary driver of codegen. Also, there are a lot more of y'all than there are of us. |
In the hope that it will inform this discussion, I’ll describe how we (IBM) are using discriminators in our APIs and code generation tools. Here’s an example use of discriminator in the API for the IBM Discovery service:
Next I'll describe how this is used in our tooling. The first thing to say about our SDK generation tooling is that it does not do any validation based on JSON schema. Some may consider this heresy, but we use the schemas in the API def purely for modeling. In Java and similar type-strict languages, the QueryAggregation schema is rendered as a public class (it is not abstract, but if the composition were "flipped" to use The
This metadata is used by our deserialization logic to trigger and guide the use of a custom TypeAdapter that is created in If there were no discriminator, the generated code would look very different. We would instead create a Sorry for the long post. I hope this has been clear and informative. |
That is a great example. Is there any reason your generated code would (or should) be different for the equivalent JSON Schema (which I have attempted to generate in the below Case 2)? Obviously, the second one is much less compact, and I am not suggesting that it is better, or that anyone should have to use that instead of Case 1 (same as your example):
Case 2:
|
@mewalig While case 2 is appealing due to the removal of the need for the extra discriminator keyword, it does make codegen a bit more challenging. The appearance of a discriminator keyword is a signal that there is a derived type scenario. It is possible that a oneOf keyword might signal the same thing, but it is a bit more opaque to recognize that the "constant" enum is the discriminator. |
@mkistler, re: #2143 (comment) It's easy to make a mistake while reimplementing discriminator to a basic JSON Schema. So, while that is possible, that's another argument why discriminator might add value. |
@ChALkeR no, since But you can make that more explicit by just need to tacking on an |
@handrews Ah.
It will fail on anything then, because anything will pass multiple branches of the |
@ChALkeR ah, yes, that is correct- that's what I get for looking at schemas 10 minutes after I wake up 😴 Either way, it's a trivial fix. Alternatively, you could replace the
selectBy: '0/petType'
oneOf:
- allOf:
- properties:
petType:
const: 'cat'
- $ref: '#/components/schemas/Cat'
- allOf:
- properties:
petType:
const: 'dog'
- $ref: '#/components/schemas/Dog'
- allOf:
- properties:
petType:
const: 'lizard'
- $ref: '#/components/schemas/Lizard'
- allOf:
- properties:
petType:
const: 'monster'
- $ref: 'https://gigantic-server.com/schemas/Monster/schema.json' |
@handrews Why is that a relative json pointer, though? Can it ascend upwards? What would be an example for that? |
@ChALkeR it can ascend upwards. And in the next draft, it can do some limited sideways motion so you can express things like "the next array element". With |
@handrews I still don't understand the usecase of relative json pointers with Or, if they are not usable with As for the example above — it looks good in principle except for the scenario where there is only one branch. Also, It's not immediately clear to me what should the annotation look like if more than one branch pass. |
@ChALkeR I'd rather not dive into an involved explanation of all possible use cases here without some indication that there's broad interest, so let's see if anyone else would like to see more. |
Agree w @ChALkeR that without further reasons/examples to the contrary, which aren't readily apparent (though, would be interested to see some if anyone can provide), it would seem more intuitive, less prone to error, more concise and easier overall to omit the |
@ChALkeR @liquidaty so I'm trying to remember exactly how this (several years old) proposal is supposed to work. It pre-dates some work we did on the processing model, and might need tweaking. This is why I don't want to spend the effort to sort all of that out unless there's a clear interest in putting something like this into OAS 3.1. If the TSC expresses interest in that, I'd be happy to go work up a proposal that I'm sure works in all cases and provides efficiency, etc. On the more narrow topic: As for why you might use a negative number, it's possible that the schema to apply to elements in an array might switch based on a property outside of the array (because it applies to the whole array). Depending on how complex the structure is, referencing the outer field from an inner subschema might be substantially less complicated than needing to replicate a bunch of structure to ensure that the field is at the "top level" of the switch. But really, it's the first case (same schema, many instance locations) that makes it flatly impossible to implement this with an absolute pointer. |
It will be easier to deprecate this once we have a codegen vocabulary that replaces these semantics. |
I'm going to mark this as "Moved to Moonwalk" and close it as there is no way we can meaningfully deprecate |
Forgot to link to where this is tracked for Moonwalk: |
I just created an example to illustrate what I think is idiomatic use of
discrimator
, so I could help answer #2141. I find it helpful to use JSON Schema Lint so I can validate in real-time. To make sure the discriminating logic worked correctly in a standard JSON Schema validator (not aware of OASdiscriminator
), I used standard JSON Schema keywords to duplicate that logic.This begs the question: now that 3.0 supports a large-enough subset of JSON Schema to describe discriminated subtypes, and 3.1 is planned to support full JSON Schema, do we still need
discriminator
?@handrews mentions the same idea here in #2031, so I think the idea deserves its own issue for future version planning.
I can see that
discriminator
might have some value for code generators. It might be easier for a code generator to be told explicitly to discriminate based on a given property, rather than relying on a JSON Schema validator to identify the matched subtype, or recognizing a pattern involvingoneOf
,enum
(orconst
), etc.But
discriminator
, as it's defined, is kind of problematic. @handrews pointed out that it ignores some available JSON Schema semantics. And I've observed, generally, that the documentation is difficult (for me) to follow, and seems to leave some questions unanswered. Without trying to create a comprehensive list here:mapping
and default name matching shown in the last example of this section.mapping
is supposed to be supplemental to name matching, rather than replacing it. In that case, is there a way for the discriminator to ensure that the discriminator property value is one of a known list of subtypes? Do we always need a separateoneOf
to validate this?mapping
) assume it's going to find the named subtype schema in#/components/schemas
, or is there some other expectation?Maybe this is more weight than we need to carry, and we'd be better off leaving this problem to JSON Schema and some new vocabulary for code generation, O-O type system mapping, or something like that.
The text was updated successfully, but these errors were encountered: