Skip to content

Commit 4ca2601

Browse files
committed
With 'oneOf' the response schema contains an extra 'type: string. Fixes #3076
1 parent ea14088 commit 4ca2601

File tree

8 files changed

+250
-5
lines changed

8 files changed

+250
-5
lines changed

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ public static boolean isComposedSchema(Schema referencedSchema) {
166166
*/
167167
public static void handleSchemaTypes(Schema<?> schema) {
168168
if (schema != null) {
169-
if (schema.getType() != null && CollectionUtils.isEmpty(schema.getTypes())) {
169+
if (schema.getType() == null && schema.getTypes() == null && schema.get$ref() == null && !isComposedSchema(schema)) {
170+
schema.addType("object");
171+
}
172+
else if (schema.getType() != null && CollectionUtils.isEmpty(schema.getTypes()) && !isComposedSchema(schema)) {
170173
schema.addType(schema.getType());
171174
}
172175
else if (schema.getItems() != null && schema.getItems().getType() != null
@@ -176,9 +179,6 @@ else if (schema.getItems() != null && schema.getItems().getType() != null
176179
if (schema.getProperties() != null) {
177180
schema.getProperties().forEach((key, value) -> handleSchemaTypes(value));
178181
}
179-
if (schema.getType() == null && schema.getTypes() == null && schema.get$ref() == null && !isComposedSchema(schema)) {
180-
schema.addType("object");
181-
}
182182
}
183183
}
184184

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2025 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package test.org.springdoc.api.v31.app12;
28+
29+
import test.org.springdoc.api.v31.AbstractSpringDocTest;
30+
31+
import org.springframework.boot.autoconfigure.SpringBootApplication;
32+
33+
public class SpringDocApp12Test extends AbstractSpringDocTest {
34+
35+
@SpringBootApplication
36+
static class SpringDocTestApp {
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package test.org.springdoc.api.v31.app12.configuration;
2+
3+
import java.util.List;
4+
5+
import com.fasterxml.jackson.annotation.JsonInclude;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.http.MediaType;
11+
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
12+
13+
@Configuration
14+
public class WebMvcConfiguration {
15+
16+
@Bean
17+
MappingJackson2HttpMessageConverter getMappingJacksonHttpMessageConverter() {
18+
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
19+
converter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_JSON));
20+
converter.setObjectMapper(new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL)
21+
);
22+
23+
return converter;
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package test.org.springdoc.api.v31.app12.controllers;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.media.Content;
5+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
6+
import test.org.springdoc.api.v31.app12.model.Cat;
7+
8+
import org.springframework.hateoas.MediaTypes;
9+
import org.springframework.hateoas.RepresentationModel;
10+
import org.springframework.http.HttpStatus;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.RequestMapping;
13+
import org.springframework.web.bind.annotation.ResponseStatus;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
@RestController
17+
@RequestMapping(path = "/")
18+
public class BasicController {
19+
20+
@GetMapping("/cat")
21+
@ResponseStatus(HttpStatus.OK)
22+
@Operation(summary = "get", description = "Provides an animal.")
23+
public String get(Cat cat) {
24+
return cat != null ? cat.getName() : "";
25+
}
26+
27+
@GetMapping("/test")
28+
@ResponseStatus(HttpStatus.OK)
29+
@Operation(summary = "get", description = "Provides a response.")
30+
@ApiResponse(content = @Content(mediaType = MediaTypes.HAL_JSON_VALUE,
31+
schema = @io.swagger.v3.oas.annotations.media.Schema(oneOf = {
32+
Integer.class
33+
})),
34+
responseCode = "200")
35+
public Object get() {
36+
return 1;
37+
}
38+
39+
// dummy
40+
public static class Response extends RepresentationModel {
41+
public Response(String v) {}
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package test.org.springdoc.api.v31.app12.controllers;
2+
3+
import org.springdoc.core.customizers.SpringDocCustomizers;
4+
import org.springdoc.core.properties.SpringDocConfigProperties;
5+
import org.springdoc.core.providers.SpringDocProviders;
6+
import org.springdoc.core.service.AbstractRequestService;
7+
import org.springdoc.core.service.GenericResponseService;
8+
import org.springdoc.core.service.OpenAPIService;
9+
import org.springdoc.core.service.OperationService;
10+
import org.springdoc.webmvc.api.OpenApiWebMvcResource;
11+
12+
import org.springframework.beans.factory.ObjectFactory;
13+
import org.springframework.web.bind.annotation.RestController;
14+
15+
@RestController
16+
public class CustomOpenApiWebMvcResource extends OpenApiWebMvcResource {
17+
18+
public CustomOpenApiWebMvcResource(ObjectFactory<OpenAPIService> openAPIBuilderObjectFactory,
19+
AbstractRequestService requestBuilder,
20+
GenericResponseService responseBuilder,
21+
OperationService operationParser,
22+
SpringDocConfigProperties springDocConfigProperties,
23+
SpringDocProviders springDocProviders,
24+
SpringDocCustomizers springDocCustomizers) {
25+
super(openAPIBuilderObjectFactory, requestBuilder, responseBuilder, operationParser, springDocConfigProperties, springDocProviders, springDocCustomizers);
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package test.org.springdoc.api.v31.app12.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
@Schema(description = "Represents a Cat class.")
7+
public class Cat {
8+
9+
@JsonUnwrapped
10+
@Schema(description = "The name.", nullable = true)
11+
private String name;
12+
13+
public Cat(String name) {
14+
this.name = name;
15+
}
16+
17+
public String getName() {
18+
return name;
19+
}
20+
21+
public void setName(String name) {
22+
this.name = name;
23+
}
24+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{
2+
"openapi": "3.1.0",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/test": {
15+
"get": {
16+
"tags": [
17+
"basic-controller"
18+
],
19+
"summary": "get",
20+
"description": "Provides a response.",
21+
"operationId": "get",
22+
"responses": {
23+
"200": {
24+
"description": "OK",
25+
"content": {
26+
"application/hal+json": {
27+
"schema": {
28+
"oneOf": [
29+
{
30+
"type": "integer",
31+
"format": "int32"
32+
}
33+
]
34+
}
35+
}
36+
}
37+
}
38+
}
39+
}
40+
},
41+
"/cat": {
42+
"get": {
43+
"tags": [
44+
"basic-controller"
45+
],
46+
"summary": "get",
47+
"description": "Provides an animal.",
48+
"operationId": "get_1",
49+
"parameters": [
50+
{
51+
"name": "cat",
52+
"in": "query",
53+
"required": true,
54+
"schema": {
55+
"$ref": "#/components/schemas/Cat"
56+
}
57+
}
58+
],
59+
"responses": {
60+
"200": {
61+
"description": "OK",
62+
"content": {
63+
"*/*": {
64+
"schema": {
65+
"type": "string"
66+
}
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
73+
},
74+
"components": {
75+
"schemas": {
76+
"Cat": {
77+
"type": "object",
78+
"description": "Represents a Cat class.",
79+
"properties": {
80+
"name": {
81+
"type": "string",
82+
"description": "The name."
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}

springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/resources/results/3.1.0/app6.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"content": {
3232
"application/json": {
3333
"schema": {
34-
"type": "string",
3534
"oneOf": [
3635
{
3736
"type": "string"

0 commit comments

Comments
 (0)