Skip to content

Commit f3de17d

Browse files
refactor: simplify type handling in ModelDeserializer (#5227)
* refactor: simplify type handling in ModelDeserializer * add tests to show behavior change * add test for reading non-text values from $ref * restore changed behavior for reading the type from a TextNode and $ref with .asText --------- Co-authored-by: Ewa Ostrowska <ewa.ostrowska@smartbear.com>
1 parent c4363f5 commit f3de17d

2 files changed

Lines changed: 119 additions & 44 deletions

File tree

modules/swagger-core/src/main/java/io/swagger/v3/core/util/ModelDeserializer.java

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@
3333

3434
public class ModelDeserializer extends JsonDeserializer<Schema> {
3535

36+
private static final String TYPE = "type";
37+
private static final String OBJECT_TYPE = "object";
38+
private static final String ARRAY_TYPE = "array";
39+
private static final String STRING_TYPE = "string";
40+
private static final String NUMBER_TYPE = "number";
41+
private static final String INTEGER_TYPE = "integer";
42+
private static final String BOOLEAN_TYPE = "boolean";
43+
private static final String ALL_OF = "allOf";
44+
private static final String ANY_OF = "anyOf";
45+
private static final String ONE_OF = "oneOf";
46+
private static final String FORMAT = "format";
47+
private static final String DATE_FORMAT = "date";
48+
private static final String DATE_TIME_FORMAT = "date-time";
49+
private static final String EMAIL_FORMAT = "email";
50+
private static final String PASSWORD_FORMAT = "password";
51+
private static final String UUID_FORMAT = "uuid";
52+
private static final String ADDITIONAL_PROPERTIES = "additionalProperties";
53+
private static final String REF = "$ref";
54+
3655
static Boolean useArbitrarySchema = false;
3756
static {
3857
if (System.getenv(Schema.USE_ARBITRARY_SCHEMA_PROPERTY) != null) {
@@ -48,7 +67,7 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt)
4867
throws IOException {
4968
JsonNode node = jp.getCodec().readTree(jp);
5069

51-
Schema schema = null;
70+
Schema schema;
5271

5372
if (openapi31) {
5473
schema = deserializeJsonSchema(node);
@@ -58,47 +77,19 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt)
5877
return new Schema().booleanSchemaValue(node.booleanValue());
5978
}
6079

61-
List<String> composed = Arrays.asList("allOf", "anyOf", "oneOf");
80+
List<String> composed = Arrays.asList(ALL_OF, ANY_OF, ONE_OF);
6281
for (String field: composed) {
6382
if (node.get(field) != null) {
6483
return Json.mapper().convertValue(node, ComposedSchema.class);
6584
}
6685
}
6786

68-
JsonNode type = node.get("type");
69-
String format = node.get("format") == null ? "" : node.get("format").textValue();
87+
JsonNode typeNode = node.get(TYPE);
7088

71-
if (type != null && "array".equals(((TextNode) type).textValue())) {
72-
schema = Json.mapper().convertValue(node, ArraySchema.class);
73-
} else if (type != null) {
74-
if (type.textValue().equals("integer")) {
75-
schema = Json.mapper().convertValue(node, IntegerSchema.class);
76-
if (StringUtils.isBlank(format)) {
77-
schema.setFormat(null);
78-
}
79-
} else if (type.textValue().equals("number")) {
80-
schema = Json.mapper().convertValue(node, NumberSchema.class);
81-
} else if (type.textValue().equals("boolean")) {
82-
schema = Json.mapper().convertValue(node, BooleanSchema.class);
83-
} else if (type.textValue().equals("string")) {
84-
if ("date".equals(format)) {
85-
schema = Json.mapper().convertValue(node, DateSchema.class);
86-
} else if ("date-time".equals(format)) {
87-
schema = Json.mapper().convertValue(node, DateTimeSchema.class);
88-
} else if ("email".equals(format)) {
89-
schema = Json.mapper().convertValue(node, EmailSchema.class);
90-
} else if ("password".equals(format)) {
91-
schema = Json.mapper().convertValue(node, PasswordSchema.class);
92-
} else if ("uuid".equals(format)) {
93-
schema = Json.mapper().convertValue(node, UUIDSchema.class);
94-
} else {
95-
schema = Json.mapper().convertValue(node, StringSchema.class);
96-
}
97-
} else if (type.textValue().equals("object")) {
98-
schema = deserializeArbitraryOrObjectSchema(node, true);
99-
}
100-
} else if (node.get("$ref") != null) {
101-
schema = new Schema().$ref(node.get("$ref").asText());
89+
if (typeNode != null) {
90+
schema = deserializeSchemaWithType(node, typeNode);
91+
} else if (node.get(REF) != null) {
92+
schema = new Schema().$ref(getRefAsString(node));
10293
} else {
10394
schema = deserializeArbitraryOrObjectSchema(node, false);
10495
}
@@ -107,12 +98,12 @@ public Schema deserialize(JsonParser jp, DeserializationContext ctxt)
10798
}
10899

109100
private Schema deserializeArbitraryOrObjectSchema(JsonNode node, boolean alwaysObject) {
110-
JsonNode additionalProperties = node.get("additionalProperties");
111-
Schema schema = null;
101+
JsonNode additionalProperties = node.get(ADDITIONAL_PROPERTIES);
102+
Schema schema;
112103
if (additionalProperties != null) {
113104
if (additionalProperties.isBoolean()) {
114105
Boolean additionalPropsBoolean = Json.mapper().convertValue(additionalProperties, Boolean.class);
115-
((ObjectNode)node).remove("additionalProperties");
106+
((ObjectNode)node).remove(ADDITIONAL_PROPERTIES);
116107
if (additionalPropsBoolean) {
117108
schema = Json.mapper().convertValue(node, MapSchema.class);
118109
} else {
@@ -121,7 +112,7 @@ private Schema deserializeArbitraryOrObjectSchema(JsonNode node, boolean alwaysO
121112
schema.setAdditionalProperties(additionalPropsBoolean);
122113
} else {
123114
Schema innerSchema = Json.mapper().convertValue(additionalProperties, Schema.class);
124-
((ObjectNode)node).remove("additionalProperties");
115+
((ObjectNode)node).remove(ADDITIONAL_PROPERTIES);
125116
MapSchema ms = Json.mapper().convertValue(node, MapSchema.class);
126117
ms.setAdditionalProperties(innerSchema);
127118
schema = ms;
@@ -143,16 +134,16 @@ private Schema deserializeJsonSchema(JsonNode node) {
143134
if (node.isBoolean()) {
144135
return new Schema().booleanSchemaValue(node.booleanValue());
145136
}
146-
JsonNode additionalProperties = node.get("additionalProperties");
147-
JsonNode type = node.get("type");
148-
Schema schema = null;
137+
JsonNode additionalProperties = node.get(ADDITIONAL_PROPERTIES);
138+
JsonNode type = node.get(TYPE);
139+
Schema schema;
149140

150141
if (type != null || additionalProperties != null) {
151142
if (type != null) {
152-
((ObjectNode)node).remove("type");
143+
((ObjectNode)node).remove(TYPE);
153144
}
154145
if (additionalProperties != null) {
155-
((ObjectNode)node).remove("additionalProperties");
146+
((ObjectNode)node).remove(ADDITIONAL_PROPERTIES);
156147
}
157148
schema = Json31.mapper().convertValue(node, JsonSchema.class);
158149
if (type instanceof TextNode) {
@@ -183,4 +174,48 @@ private Schema deserializeJsonSchema(JsonNode node) {
183174
}
184175
return schema;
185176
}
177+
178+
private Schema deserializeSchemaWithType(JsonNode node, JsonNode typeNode) {
179+
Schema schema = null;
180+
String type = ((TextNode) typeNode).textValue();
181+
String format = node.get(FORMAT) == null ? "" : getNodeAsString(node, FORMAT);
182+
183+
if (type.equals(ARRAY_TYPE)) {
184+
schema = Json.mapper().convertValue(node, ArraySchema.class);
185+
} else if (type.equals(INTEGER_TYPE)) {
186+
schema = Json.mapper().convertValue(node, IntegerSchema.class);
187+
if (StringUtils.isBlank(format)) {
188+
schema.setFormat(null);
189+
}
190+
} else if (type.equals(NUMBER_TYPE)) {
191+
schema = Json.mapper().convertValue(node, NumberSchema.class);
192+
} else if (type.equals(BOOLEAN_TYPE)) {
193+
schema = Json.mapper().convertValue(node, BooleanSchema.class);
194+
} else if (type.equals(STRING_TYPE)) {
195+
if (DATE_FORMAT.equals(format)) {
196+
schema = Json.mapper().convertValue(node, DateSchema.class);
197+
} else if (DATE_TIME_FORMAT.equals(format)) {
198+
schema = Json.mapper().convertValue(node, DateTimeSchema.class);
199+
} else if (EMAIL_FORMAT.equals(format)) {
200+
schema = Json.mapper().convertValue(node, EmailSchema.class);
201+
} else if (PASSWORD_FORMAT.equals(format)) {
202+
schema = Json.mapper().convertValue(node, PasswordSchema.class);
203+
} else if (UUID_FORMAT.equals(format)) {
204+
schema = Json.mapper().convertValue(node, UUIDSchema.class);
205+
} else {
206+
schema = Json.mapper().convertValue(node, StringSchema.class);
207+
}
208+
} else if (type.equals(OBJECT_TYPE)) {
209+
schema = deserializeArbitraryOrObjectSchema(node, true);
210+
}
211+
return schema;
212+
}
213+
214+
private String getNodeAsString(JsonNode jsonNode, String field) {
215+
return jsonNode.get(field).textValue();
216+
}
217+
218+
private String getRefAsString(JsonNode jsonNode) {
219+
return jsonNode.get(REF).asText();
220+
}
186221
}

modules/swagger-core/src/test/java/io/swagger/v3/core/deserialization/JsonDeserializationTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.swagger.v3.oas.models.security.SecurityRequirement;
2020
import io.swagger.v3.oas.models.security.SecurityScheme;
2121
import org.apache.commons.io.FileUtils;
22+
import org.testng.annotations.DataProvider;
2223
import org.testng.annotations.Test;
2324

2425
import java.io.File;
@@ -85,6 +86,45 @@ public void testObjectProperty() throws IOException {
8586
assertEquals("objectProperty", result.getTitle());
8687
}
8788

89+
@DataProvider(name = "nonTextSchemaTypes")
90+
public Object[][] nonTextSchemaTypes() {
91+
return new Object[][]{
92+
{"{\"type\":1}"},
93+
{"{\"type\":true}"},
94+
{"{\"type\":{}}"},
95+
{"{\"type\":[]}"}
96+
};
97+
}
98+
99+
@Test(dataProvider = "nonTextSchemaTypes")
100+
public void deserializeSchemaWithNonTextTypeFailsWithLegacyCastException(String json) throws IOException {
101+
try {
102+
m.readValue(json, Schema.class);
103+
} catch (RuntimeException e) {
104+
assertTrue(e instanceof ClassCastException);
105+
return;
106+
}
107+
108+
assertTrue(false, "Expected ClassCastException");
109+
}
110+
111+
@DataProvider(name = "nonTextSchemaRef")
112+
public Object[][] nonTextSchemaRef() {
113+
return new Object[][]{
114+
{"{\"$ref\":1}", "#/components/schemas/1"},
115+
{"{\"$ref\":true}", "#/components/schemas/true"},
116+
{"{\"$ref\":{}}", "#/components/schemas/"},
117+
{"{\"$ref\":[]}", "#/components/schemas/"}
118+
};
119+
}
120+
121+
@Test(dataProvider = "nonTextSchemaRef")
122+
public void deserializeSchemaWithNonTextRef(String json, String expectedRef) throws IOException {
123+
Schema schema = m.readValue(json, Schema.class);
124+
125+
assertEquals(schema.get$ref(), expectedRef);
126+
}
127+
88128
@Test(description = "it should deserialize nested ObjectProperty(s)")
89129
public void testNestedObjectProperty() throws IOException {
90130
final String json = "{\n" +

0 commit comments

Comments
 (0)