Skip to content

Commit 7060d3a

Browse files
committed
fix: respect @JsonUnwrapped & @Schema on props not fields only
1 parent 28c66e1 commit 7060d3a

File tree

12 files changed

+304
-7
lines changed

12 files changed

+304
-7
lines changed

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

+19-6
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
import java.util.Set;
3838

3939
import com.fasterxml.jackson.annotation.JsonUnwrapped;
40+
import com.fasterxml.jackson.databind.BeanDescription;
4041
import com.fasterxml.jackson.databind.JavaType;
42+
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
4143
import io.swagger.v3.core.converter.AnnotatedType;
4244
import io.swagger.v3.core.converter.ModelConverter;
4345
import io.swagger.v3.core.converter.ModelConverterContext;
@@ -120,17 +122,28 @@ else if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getSi
120122
public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
121123
JavaType javaType = springDocObjectMapper.jsonMapper().constructType(type.getType());
122124
if (javaType != null) {
123-
for (Field field : FieldUtils.getAllFields(javaType.getRawClass())) {
124-
if (field.isAnnotationPresent(JsonUnwrapped.class)) {
125+
BeanDescription javaTypeIntrospection = springDocObjectMapper.jsonMapper().getDeserializationConfig().introspect(javaType);
126+
for (BeanPropertyDefinition property : javaTypeIntrospection.findProperties()) {
127+
boolean isUnwrapped = (property.getField() != null && property.getField().hasAnnotation(JsonUnwrapped.class)) ||
128+
(property.getGetter() != null && property.getGetter().hasAnnotation(JsonUnwrapped.class));
129+
130+
if (isUnwrapped) {
125131
if (!TypeNameResolver.std.getUseFqn())
126132
PARENT_TYPES_TO_IGNORE.add(javaType.getRawClass().getSimpleName());
127133
else
128134
PARENT_TYPES_TO_IGNORE.add(javaType.getRawClass().getName());
129135
}
130-
else if (field.isAnnotationPresent(io.swagger.v3.oas.annotations.media.Schema.class)) {
131-
io.swagger.v3.oas.annotations.media.Schema declaredSchema = field.getDeclaredAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
132-
if (ArrayUtils.isNotEmpty(declaredSchema.oneOf()) || ArrayUtils.isNotEmpty(declaredSchema.allOf())) {
133-
TYPES_TO_SKIP.add(field.getType().getSimpleName());
136+
else {
137+
io.swagger.v3.oas.annotations.media.Schema declaredSchema = null;
138+
if (property.getField() != null) {
139+
declaredSchema = property.getField().getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
140+
} else if (property.getGetter() != null) {
141+
declaredSchema = property.getGetter().getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
142+
}
143+
144+
if (declaredSchema != null &&
145+
(ArrayUtils.isNotEmpty(declaredSchema.oneOf()) || ArrayUtils.isNotEmpty(declaredSchema.allOf()))) {
146+
TYPES_TO_SKIP.add(property.getPrimaryType().getRawClass().getSimpleName());
134147
}
135148
}
136149
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package test.org.springdoc.api.v30.app239;
2+
3+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
4+
5+
public class RootModel {
6+
7+
private Integer rootProperty;
8+
9+
private UnwrappedModel unwrappedModel;
10+
11+
public Integer getRootProperty() {
12+
return rootProperty;
13+
}
14+
15+
public void setRootProperty(Integer rootProperty) {
16+
this.rootProperty = rootProperty;
17+
}
18+
19+
@JsonUnwrapped
20+
public UnwrappedModel getUnwrappedModel() {
21+
return unwrappedModel;
22+
}
23+
24+
public void setUnwrappedModel(UnwrappedModel unwrappedModel) {
25+
this.unwrappedModel = unwrappedModel;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v30.app239;
26+
27+
import org.springframework.boot.autoconfigure.SpringBootApplication;
28+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
29+
30+
public class SpringDocApp239Test extends AbstractSpringDocV30Test {
31+
32+
@SpringBootApplication
33+
static class SpringDocTestApp {}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package test.org.springdoc.api.v30.app239;
2+
3+
import org.springframework.web.bind.annotation.GetMapping;
4+
import org.springframework.web.bind.annotation.RequestMapping;
5+
import org.springframework.web.bind.annotation.RestController;
6+
7+
@RestController
8+
@RequestMapping("/api")
9+
public class TestController {
10+
11+
@GetMapping
12+
public RootModel getRootModel() {
13+
return new RootModel();
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package test.org.springdoc.api.v30.app239;
2+
3+
public class UnwrappedModel {
4+
5+
private Integer unwrappedProperty;
6+
7+
public Integer getUnwrappedProperty() {
8+
return unwrappedProperty;
9+
}
10+
11+
public void setUnwrappedProperty(Integer unwrappedProperty) {
12+
this.unwrappedProperty = unwrappedProperty;
13+
}
14+
}

Diff for: springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v31/app224/RootModel.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ public class RootModel {
66

77
private Integer rootProperty;
88

9-
@JsonUnwrapped
109
private UnwrappedModel unwrappedModel;
1110

1211
public Integer getRootProperty() {
@@ -17,6 +16,7 @@ public void setRootProperty(Integer rootProperty) {
1716
this.rootProperty = rootProperty;
1817
}
1918

19+
@JsonUnwrapped
2020
public UnwrappedModel getUnwrappedModel() {
2121
return unwrappedModel;
2222
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package test.org.springdoc.api.v31.app239;
2+
3+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
4+
5+
public class RootModel {
6+
7+
private Integer rootProperty;
8+
9+
@JsonUnwrapped
10+
private UnwrappedModel unwrappedModel;
11+
12+
public Integer getRootProperty() {
13+
return rootProperty;
14+
}
15+
16+
public void setRootProperty(Integer rootProperty) {
17+
this.rootProperty = rootProperty;
18+
}
19+
20+
public UnwrappedModel getUnwrappedModel() {
21+
return unwrappedModel;
22+
}
23+
24+
public void setUnwrappedModel(UnwrappedModel unwrappedModel) {
25+
this.unwrappedModel = unwrappedModel;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v31.app239;
26+
27+
import org.springframework.boot.autoconfigure.SpringBootApplication;
28+
import test.org.springdoc.api.v31.AbstractSpringDocTest;
29+
30+
public class SpringDocApp239Test extends AbstractSpringDocTest {
31+
32+
@SpringBootApplication
33+
static class SpringDocTestApp {}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package test.org.springdoc.api.v31.app239;
2+
3+
import org.springframework.web.bind.annotation.GetMapping;
4+
import org.springframework.web.bind.annotation.RequestMapping;
5+
import org.springframework.web.bind.annotation.RestController;
6+
7+
@RestController
8+
@RequestMapping("/api")
9+
public class TestController {
10+
11+
@GetMapping
12+
public RootModel getRootModel() {
13+
return new RootModel();
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package test.org.springdoc.api.v31.app239;
2+
3+
public class UnwrappedModel {
4+
5+
private Integer unwrappedProperty;
6+
7+
public Integer getUnwrappedProperty() {
8+
return unwrappedProperty;
9+
}
10+
11+
public void setUnwrappedProperty(Integer unwrappedProperty) {
12+
this.unwrappedProperty = unwrappedProperty;
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"openapi": "3.0.1",
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+
"/api": {
15+
"get": {
16+
"tags": [
17+
"test-controller"
18+
],
19+
"operationId": "getRootModel",
20+
"responses": {
21+
"200": {
22+
"description": "OK",
23+
"content": {
24+
"*/*": {
25+
"schema": {
26+
"$ref": "#/components/schemas/RootModel"
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
33+
}
34+
},
35+
"components": {
36+
"schemas": {
37+
"RootModel": {
38+
"type": "object",
39+
"properties": {
40+
"rootProperty": {
41+
"type": "integer",
42+
"format": "int32"
43+
},
44+
"unwrappedProperty": {
45+
"type": "integer",
46+
"format": "int32"
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
"/api": {
15+
"get": {
16+
"tags": [
17+
"test-controller"
18+
],
19+
"operationId": "getRootModel",
20+
"responses": {
21+
"200": {
22+
"description": "OK",
23+
"content": {
24+
"*/*": {
25+
"schema": {
26+
"$ref": "#/components/schemas/RootModel"
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
33+
}
34+
},
35+
"components": {
36+
"schemas": {
37+
"RootModel": {
38+
"type": "object",
39+
"properties": {
40+
"rootProperty": {
41+
"type": "integer",
42+
"format": "int32"
43+
},
44+
"unwrappedProperty": {
45+
"type": "integer",
46+
"format": "int32"
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)