Skip to content

Commit fb92709

Browse files
committed
@JsonUnwrapped is ignored in new version of lib. Fixes #2856
1 parent 06b297a commit fb92709

File tree

11 files changed

+409
-3
lines changed

11 files changed

+409
-3
lines changed

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PolymorphicModelConverter.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,17 @@
3131
import java.util.ArrayList;
3232
import java.util.Collection;
3333
import java.util.Collections;
34+
import java.util.HashSet;
3435
import java.util.Iterator;
3536
import java.util.List;
37+
import java.util.Set;
3638

3739
import com.fasterxml.jackson.annotation.JsonUnwrapped;
3840
import com.fasterxml.jackson.databind.JavaType;
3941
import io.swagger.v3.core.converter.AnnotatedType;
4042
import io.swagger.v3.core.converter.ModelConverter;
4143
import io.swagger.v3.core.converter.ModelConverterContext;
44+
import io.swagger.v3.core.jackson.TypeNameResolver;
4245
import io.swagger.v3.core.util.AnnotationsUtils;
4346
import io.swagger.v3.oas.models.Components;
4447
import io.swagger.v3.oas.models.media.ComposedSchema;
@@ -63,12 +66,12 @@ public class PolymorphicModelConverter implements ModelConverter {
6366
/**
6467
* The constant PARENT_TYPES_TO_IGNORE.
6568
*/
66-
private static final List<String> PARENT_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());
69+
private static final Set<String> PARENT_TYPES_TO_IGNORE = Collections.synchronizedSet(new HashSet<>());
6770

6871
/**
6972
* The constant PARENT_TYPES_TO_IGNORE.
7073
*/
71-
private static final List<String> TYPES_TO_SKIP = Collections.synchronizedList(new ArrayList<>());
74+
private static final Set<String> TYPES_TO_SKIP = Collections.synchronizedSet(new HashSet<>());
7275

7376
static {
7477
PARENT_TYPES_TO_IGNORE.add("JsonSchema");
@@ -119,7 +122,10 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
119122
if (javaType != null) {
120123
for (Field field : FieldUtils.getAllFields(javaType.getRawClass())) {
121124
if (field.isAnnotationPresent(JsonUnwrapped.class)) {
122-
PARENT_TYPES_TO_IGNORE.add(javaType.getRawClass().getSimpleName());
125+
if (!TypeNameResolver.std.getUseFqn())
126+
PARENT_TYPES_TO_IGNORE.add(javaType.getRawClass().getSimpleName());
127+
else
128+
PARENT_TYPES_TO_IGNORE.add(javaType.getRawClass().getName());
123129
}
124130
else if (field.isAnnotationPresent(io.swagger.v3.oas.annotations.media.Schema.class)) {
125131
io.swagger.v3.oas.annotations.media.Schema declaredSchema = field.getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package test.org.springdoc.api.v30.app237;
2+
3+
import io.swagger.v3.core.util.PrimitiveType;
4+
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.info.Info;
6+
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
@Configuration
11+
public class OpenApiConfiguration {
12+
13+
public OpenApiConfiguration() {
14+
// LocalTime support
15+
PrimitiveType.enablePartialTime();
16+
}
17+
18+
private OpenAPI standardOpenApi() {
19+
String title = getClass().getPackage().getImplementationTitle();
20+
String version = getClass().getPackage().getImplementationVersion();
21+
return new OpenAPI()
22+
.info(new Info()
23+
.title(title == null ? "DEV" : title)
24+
.version(version == null ? "local" : version));
25+
}
26+
27+
@Bean
28+
public OpenAPI devOpenApi() {
29+
return standardOpenApi();
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.v30.app237;
28+
29+
import io.swagger.v3.core.jackson.TypeNameResolver;
30+
import org.junit.jupiter.api.AfterAll;
31+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
32+
33+
import org.springframework.boot.autoconfigure.SpringBootApplication;
34+
import org.springframework.test.context.TestPropertySource;
35+
36+
@TestPropertySource(properties = "springdoc.use-fqn=true")
37+
public class SpringDocApp237Test extends AbstractSpringDocV30Test {
38+
39+
@AfterAll
40+
static void restore() {
41+
TypeNameResolver.std.setUseFqn(false);
42+
}
43+
44+
@SpringBootApplication
45+
static class SpringDocTestApp {}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package test.org.springdoc.api.v30.app237;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
5+
import test.org.springdoc.api.v30.app237.dto.Example;
6+
7+
import org.springframework.http.HttpStatus;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.ResponseStatus;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
public class UnwrappedController {
14+
15+
@GetMapping("/")
16+
@ResponseStatus(HttpStatus.OK)
17+
@Operation(summary = "Simple get task")
18+
@ApiResponse(responseCode = "200", description = "Task has been started")
19+
@ApiResponse(responseCode = "404", description = "Task was not found")
20+
@ApiResponse(responseCode = "409", description = "Task is already running")
21+
public Example exampleGet() {
22+
return new Example(new Example.Wrapped("Some value"), 1);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package test.org.springdoc.api.v30.app237.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
public record Example(
7+
@JsonUnwrapped
8+
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
9+
Wrapped unwrapped,
10+
11+
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Some description")
12+
Integer number
13+
) {
14+
public record Wrapped(
15+
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Some description of value")
16+
String value
17+
) {
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package test.org.springdoc.api.v31.app237;
2+
3+
import io.swagger.v3.core.util.PrimitiveType;
4+
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.info.Info;
6+
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
@Configuration
11+
public class OpenApiConfiguration {
12+
13+
public OpenApiConfiguration() {
14+
// LocalTime support
15+
PrimitiveType.enablePartialTime();
16+
}
17+
18+
private OpenAPI standardOpenApi() {
19+
String title = getClass().getPackage().getImplementationTitle();
20+
String version = getClass().getPackage().getImplementationVersion();
21+
return new OpenAPI()
22+
.info(new Info()
23+
.title(title == null ? "DEV" : title)
24+
.version(version == null ? "local" : version));
25+
}
26+
27+
@Bean
28+
public OpenAPI devOpenApi() {
29+
return standardOpenApi();
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.app237;
28+
29+
import io.swagger.v3.core.jackson.TypeNameResolver;
30+
import org.junit.jupiter.api.AfterAll;
31+
import test.org.springdoc.api.v31.AbstractSpringDocTest;
32+
33+
import org.springframework.boot.autoconfigure.SpringBootApplication;
34+
import org.springframework.test.context.TestPropertySource;
35+
36+
@TestPropertySource(properties = "springdoc.use-fqn=true")
37+
public class SpringDocApp237Test extends AbstractSpringDocTest {
38+
39+
@AfterAll
40+
static void restore() {
41+
TypeNameResolver.std.setUseFqn(false);
42+
}
43+
44+
@SpringBootApplication
45+
static class SpringDocTestApp {}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package test.org.springdoc.api.v31.app237;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
5+
import test.org.springdoc.api.v31.app237.dto.Example;
6+
7+
import org.springframework.http.HttpStatus;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.ResponseStatus;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
public class UnwrappedController {
14+
15+
@GetMapping("/")
16+
@ResponseStatus(HttpStatus.OK)
17+
@Operation(summary = "Simple get task")
18+
@ApiResponse(responseCode = "200", description = "Task has been started")
19+
@ApiResponse(responseCode = "404", description = "Task was not found")
20+
@ApiResponse(responseCode = "409", description = "Task is already running")
21+
public Example exampleGet() {
22+
return new Example(new Example.Wrapped("Some value"), 1);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package test.org.springdoc.api.v31.app237.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
public record Example(
7+
@JsonUnwrapped
8+
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
9+
Wrapped unwrapped,
10+
11+
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Some description")
12+
Integer number
13+
) {
14+
public record Wrapped(
15+
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Some description of value")
16+
String value
17+
) {
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "DEV",
5+
"version": "local"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/": {
15+
"get": {
16+
"tags": [
17+
"unwrapped-controller"
18+
],
19+
"summary": "Simple get task",
20+
"operationId": "exampleGet",
21+
"responses": {
22+
"200": {
23+
"description": "Task has been started",
24+
"content": {
25+
"*/*": {
26+
"schema": {
27+
"$ref": "#/components/schemas/test.org.springdoc.api.v30.app237.dto.Example"
28+
}
29+
}
30+
}
31+
},
32+
"404": {
33+
"description": "Task was not found",
34+
"content": {
35+
"*/*": {
36+
"schema": {
37+
"$ref": "#/components/schemas/test.org.springdoc.api.v30.app237.dto.Example"
38+
}
39+
}
40+
}
41+
},
42+
"409": {
43+
"description": "Task is already running",
44+
"content": {
45+
"*/*": {
46+
"schema": {
47+
"$ref": "#/components/schemas/test.org.springdoc.api.v30.app237.dto.Example"
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
}
55+
},
56+
"components": {
57+
"schemas": {
58+
"test.org.springdoc.api.v30.app237.dto.Example": {
59+
"required": [
60+
"number",
61+
"value"
62+
],
63+
"type": "object",
64+
"properties": {
65+
"value": {
66+
"type": "string",
67+
"description": "Some description of value"
68+
},
69+
"number": {
70+
"type": "integer",
71+
"description": "Some description",
72+
"format": "int32"
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)