Conversation
|
Could we share what tools that prefer a cyclic polymorphic structure? As shared in #5314 (comment) my experience is that most actually prefer it to not exists, and that even OAS documentation has previously documented it as "the two different polymorphic structures (e.g., the To me
feels like the opposite of how the annotations function in general? Isn't the general idea that first heuristics are used to derive the OAS structure, but that we then provide these annotations so that a user can force the structure to follow something that the user explicitly would like to correct? Now we instead force the generation to follow the internal interpretation, and ignore any explicit structure that the user has asked for? Basically copying what happened in springdoc/springdoc-openapi#2915? If someone would like to restore the previous behavior, have we thought about making that easily-achievable? I would recommend ensuring that there are good extensions points for it, and that it is illustrated with a test similar to #5030. |
Changes resolveSubtypes() from private to protected so a downstream ModelResolver subclass can override it and opt out of the automatic @JsonSubTypes -> oneOf composition (restoring the previous behavior for consumers who dislike the allOf+oneOf hybrid). Adds JsonSubTypesAndSchemaOneOfTest.extensionPointAllowsSubclassToRestorePreviousBehavior demonstrating the escape hatch, per review feedback on swagger-api#5320.
When a class declares an explicit @Schema(oneOf = ...), it fully defines the polymorphic structure and now takes precedence over the @JsonSubTypes-derived composition in resolveSubtypes(). This avoids the recursive allOf<->oneOf structure and honors the user-explicit @Schema(oneOf), per review feedback on swagger-api#5320. Also always process the explicit @Schema(oneOf) even when @JsonSubTypes is present (previously it was skipped). Update affected test expectations: composed parents keep only shared discriminator properties and children are plain object schemas without a recursive allOf->parent reference (ComposedSchemaTest#2620, Ticket3197Test, JsonSubTypesAndSchemaOneOfTest).
|
Thanks for the detailed review, @Mattias-Sehlstedt. I've addressed both of your points with two follow-up commits on this branch. 1. Extension point to restore the previous behavior (easy opt-out)
which shows a subclass override preventing the 2. Explicit
|
|
What should be default I will leave to swagger-core to decide. I am very happy as long as there is an easy way to control the behavior with configurations/extensions. As a human I prefer the more logical I can see if I have time to investigate further the history behind it and what might actually be considered best-practice. Do note that any findings should not impact this PR in any way, since it gives full control to the user, and thus findings will not impact code but at most recommendations for users. |
|
I've searched around a bit and all I can find point to the best approach being to not have them together. If one wants to create a "link" between the parent and the children, the best way to express that is with the One can for example query de-detoxified Stack overflow (read: AI) with a biased prompt "I want to strengthen the relation between parent and child with Pet:
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
discriminator:
propertyName: petType
mapping:
dog: '#/components/schemas/Dog'
cat: '#/components/schemas/Cat'So my personal suggestion would be to not make |
|
@Mattias-Sehlstedt, thanks for your input! Now let's wait for a reply from the team regarding the @Schema/@JsonSubTypes allOf/oneOf relationship. |
cccad3f to
dea7190
Compare
Identical @JsonSubTypes entries (and Jackson's AnnotationIntrospectorPair) can report the same subtype more than once. Once a polymorphic parent is composed into a oneOf (see swagger-api#5320), each duplicate entry becomes a repeated $ref in the composed schema, e.g. two $refs to the same subtype. resolveSubtypes() now drops duplicate subtypes via a new removeDuplicateSubTypes() helper, preserving declaration order. Note on testing: on 3.0.0 alone the parent is not composed into a oneOf, so duplicate entries have no observable effect and there is no failing test to add here. The added test asserts a duplicate-free oneOf when a oneOf is present (it passes trivially on 3.0.0) and only starts failing without this fix once the @JsonSubTypes -> oneOf composition from swagger-api#5320 is applied, where the oneOf would contain 3 entries instead of 2.
Identical @JsonSubTypes entries (and Jackson's AnnotationIntrospectorPair) can report the same subtype more than once. Once a polymorphic parent is composed into a oneOf (see swagger-api#5320), each duplicate entry becomes a repeated $ref in the composed schema, e.g. two $refs to the same subtype. resolveSubtypes() now drops duplicate subtypes via a new removeDuplicateSubTypes() helper, preserving declaration order.
Adds an opt-in (json-subtypes-oneof system property / ModelResolver.jsonSubTypesOneOf) that composes a @JsonSubTypes or @Schema(subTypes) parent into a self-referencing oneOf: the parent gets a oneOf to its subtypes while the subtypes keep allOf to the parent. The cyclic allOf <-> oneOf shape is off by default because swagger-ui, openapi-generator and others dislike it, so the default output is unchanged from 3.0.0. When enabled, resolveSubtypes() replaces the parent model with a ComposedSchema whose oneOf references the resolved subtypes (using the resolved @JsonView-aware names) and re-reads the parent from the context; an explicit @Schema(oneOf) wins over the automatic composition to avoid a recursive allOf <-> oneOf. resolveSubtypes() is now protected instead of private, providing an extension point for downstream ModelResolver subclasses that need to customize or disable subtype resolution (see swagger-api#5030).
dea7190 to
b625bb8
Compare
|
Thanks @Mattias-Sehlstedt, that feedback changed the direction of this PR. Summary of where it landed: 1. Cyclic 2. Explicit 3. Extension point + test. On "which tools prefer a cyclic structure": I don't have a concrete list to cite. Your references (swagger-ui, openapi-generator) and the discriminator-mapping approach in #4983 settled it for me, so the composition is opt-in rather than default. With this PR a
If the team wants the linked form to be the default, that's a small follow-up: flip the flag's default. |
Pull Request
Thank you for contributing to swagger-core!
Please fill out the following information to help us review your PR efficiently.
Description
Adds an opt-in that composes a
@JsonSubTypes/@Schema(subTypes)polymorphic parent into a self-referencingoneOf: the parent gets aoneOfto its subtypes while the subtypes keepallOfto the parent.This cyclic
allOf<->oneOfshape is off by default because several tools (swagger-ui, openapi-generator, ...) dislike it, so the default output is unchanged from3.0.0. It can be enabled with-Djson-subtypes-oneoforModelResolver.jsonSubTypesOneOf = true.When enabled, the parent model is replaced with a
ComposedSchemawhoseoneOfreferences the resolved subtypes (using the resolved@JsonView-aware names); an explicit@Schema(oneOf)wins over the automatic composition to avoid a recursiveallOf<->oneOf.resolveSubtypes()is nowprotectedinstead ofprivate, providing an extension point for downstream subclasses (see #5030).Related: #4991, #5030, #5331, #5332
Type of Change
Checklist
Screenshots / Additional Context
3.0.0; no existing expectations change.JsonSubTypesAndSchemaOneOfTest(composition when enabled, no composition by default, and theresolveSubtypes()override opt-out).WRAPPER_OBJECTparent, the composedoneOfappears nested inside the wrapping property; that interaction is handled separately in fix: keep oneOf when WRAPPER_OBJECT wrapping is applied #5331.