Skip to content

fix: keep oneOf when WRAPPER_OBJECT wrapping is applied - #5331

Merged
ewaostrowska merged 2 commits into
swagger-api:3.0.0from
vpelikh:upstream-pr-1-wrapper-object
Oct 14, 2026
Merged

ewaostrowska merged 2 commits into
swagger-api:3.0.0from
vpelikh:upstream-pr-1-wrapper-object

Conversation

@vpelikh

@vpelikh vpelikh commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A schema that already carries a oneOf now keeps it when Jackson's WRAPPER_OBJECT type info is applied. Previously the composition was silently dropped and the schema became a plain object with a single property.

Problem

ModelResolver.resolveWrapping(...) builds a new wrapper schema and nests the model inside it:

Schema wrapperSchema = openapi31 ? new JsonSchema().typesItem("object") : new ObjectSchema();
wrapperSchema.name(model.getName());
wrapperSchema.addProperties(name, model);
return wrapperSchema;

The wrapper copies only the name and the single property. If model had a oneOf (from @Schema(oneOf = ...), @Schema(subTypes = ...) or @JsonSubTypes), it is lost: the returned schema is an ObjectSchema, not a ComposedSchema, and the oneOf is gone.

Reproduction (@JsonTypeInfo(include = WRAPPER_OBJECT) + @Schema(oneOf)):

result
before ObjectSchema, properties [Pet], no oneOf
after ComposedSchema, oneOf = 2 refs, wrapper property present

Change

When the model already has a non-empty oneOf, add the wrapper property directly to the model and return it, instead of nesting it in a wrapper schema:

if (model.getOneOf() != null && !model.getOneOf().isEmpty()) {
    model.addProperties(name, newObjectSchema());
    return model;
}

The empty-object-schema expression was already used once in this method, so instead of repeating it a second time it is extracted to a small helper:

/** An empty object schema for the current OpenAPI version. */
private Schema newObjectSchema() {
    return openapi31 ? new JsonSchema().typesItem("object") : new ObjectSchema();
}

Behavior is unchanged for models without oneOf.

Tests

Adds WrapperObjectOneOfTest. It fails before the change (ObjectSchema with no oneOf) and passes after it.

Notes

This is the small stack split out of #5320. It is a standalone bug fix, reproducible on unmodified 3.0.0, and does not depend on @JsonSubTypes work.

@ewaostrowska

Copy link
Copy Markdown
Contributor

Thank you for the PR @vpelikh!

I tested this and pushed a small change on top: I removed the empty placeholder property, because it added noise to the schema. I also added tests (no extra property next to oneOf, resolving the same class twice, OpenAPI 3.1, and a model without oneOf is still wrapped as before).

Before the PR, Pet was nested one level down under the wrapper key:

Pet:
  type: object
  properties:
    Pet:
      type: object
      properties:
        petType: {type: string}
      oneOf: [Dog, Cat]

After the PR, as submitted:

Pet:
  type: object
  properties:
    petType: {type: string}
    Pet: {type: object}   # empty placeholder
  oneOf: [Dog, Cat]

The oneOf is back at the top level, which is what tools expect for a polymorphic type, so this is an improvement. But Pet: {type: object} describes nothing and does not match the real JSON, so I removed it. The model is now returned unchanged when it has a oneOf. This also avoids changing the model in place.

The schema is still not fully correct. With WRAPPER_OBJECT, the JSON is:

{"Dog": {"petType": "dog", "bark": "woof"}}

To match it, each oneOf branch should be wrapped, not the base type:

Pet:
  oneOf:
    - type: object
      required: [Dog]
      properties:
        Dog: {$ref: '#/components/schemas/Dog'}
    - type: object
      required: [Cat]
      properties:
        Cat: {$ref: '#/components/schemas/Cat'}

That needs a more complex handler (subtype names, $ref targets, 3.0 and 3.1) and more tests, so I think it is out of scope here. I added a TODO in the code.

@vpelikh

vpelikh commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

Hey @ewaostrowska. Thanks for reviewing this and other PRs. Are you okay with this PR as is, or would you like me to make some changes?

@ewaostrowska
ewaostrowska force-pushed the upstream-pr-1-wrapper-object branch from a7bcd87 to a5ecee0 Compare October 14, 2026 07:32
@ewaostrowska

Copy link
Copy Markdown
Contributor

@vpelikh I think the current state should be fine for now, but let's wait on review from @djankows or @daniel-kmiecik

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The implementation preserves oneOf by removing required WRAPPER_OBJECT semantics, producing a schema that does not match serialized values.

1 open finding
What changed in this PR

Attempts to preserve oneOf schemas during Jackson WRAPPER_OBJECT resolution.

Changes:

  • Bypasses wrapper creation when oneOf is present.
  • Adds regression tests for OpenAPI 3.0 and 3.1.
File Description
ModelResolver.java Alters wrapping behavior for composed schemas.
WrapperObjectOneOfTest.java Tests oneOf preservation and repeated resolution.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

vpelikh and others added 2 commits October 14, 2026 13:38
ModelResolver.resolveWrapping() nested the whole model inside a new wrapper
schema that only copied the name and properties, so a model carrying a oneOf
(from @Schema(oneOf) or @JsonSubTypes) lost its composition and became a plain
object schema with a single property.

When the model already has a non-empty oneOf, add the wrapper property
directly to the model and return it, preserving the composition. Behavior is
unchanged for models without oneOf.

Adds WrapperObjectOneOfTest, which fails before the fix (ObjectSchema with no
oneOf) and passes after it.
@ewaostrowska
ewaostrowska force-pushed the upstream-pr-1-wrapper-object branch from a5ecee0 to fdce351 Compare October 14, 2026 11:38
@ewaostrowska
ewaostrowska merged commit c0f7a52 into swagger-api:3.0.0 Oct 14, 2026
8 checks passed
@vpelikh
vpelikh deleted the upstream-pr-1-wrapper-object branch October 19, 2026 06:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants