Repository: swagger-api/swagger-parser
Pull request #2202 (2025-06-16 14:29:55)

fix: add explicit-object-schema flag (#2113)


Patch:
diff --git a/README.md b/README.md
index dddec7d3..bc16a726 100644
--- a/README.md
+++ b/README.md
@@ -654,6 +654,21 @@ components:
           type: string
           example: "94022"
 ```
+#### 5. explicitObjectSchema:
+
+```java
+ParseOptions parseOptions = new ParseOptions();
+parseOptions.setResolve(true); // implicit
+parseOptions.setResolveFully(true);
+parseOptions.setResolveCombinators(false);
+parseOptions.setExplicitObjectSchema(false); // default is true
+final OpenAPI openAPI = new OpenAPIV3Parser().read("a.yaml", null, parseOptions);
+```
+
+This option allows you to customize the processing of schema properties when the type is not specified. By default it is set to `true`.
+- `true` : when the type is not defined for property, ‘object’ is set.
+- `false` : the property remains undefined when no type is specified
+It's only applied when `resolveFully` is set to `true`.
 
 ### Extensions
 This project has a core artifact--`swagger-parser`, which uses Java Service Provider Interface (SPI) so additional extensions can be added. 
diff --git a/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java b/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
index 777d4877..0bdbebd5 100644
--- a/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
+++ b/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
@@ -22,6 +22,7 @@ public class ParseOptions {
     private List<String> remoteRefAllowList;
     private List<String> remoteRefBlockList;
     private boolean explicitStyleAndExplode = true;
+    private boolean explicitObjectSchema = true;
 
 
     public boolean isResolve() {
@@ -179,4 +180,12 @@ public class ParseOptions {
         this.explicitStyleAndExplode = explicitStyleAndExplode;
     }
 
+    public boolean isExplicitObjectSchema() {
+        return explicitObjectSchema;
+    }
+
+    public void setExplicitObjectSchema(boolean explicitObjectSchema) {
+        this.explicitObjectSchema = explicitObjectSchema;
+    }
+
 }
diff --git a/modules/swagger-parser-v2-converter/src/main/java/io/swagger/v3/parser/converter/SwaggerConverter.java b/modules/swagger-parser-v2-converter/src/main/java/io/swagger/v3/parser/converter/SwaggerConverter.java
index d4d2b189..019aeaf6 100644
--- a/modules/swagger-parser-v2-converter/src/main/java/io/swagger/v3/parser/converter/SwaggerConverter.java
+++ b/modules/swagger-parser-v2-converter/src/main/java/io/swagger/v3/parser/converter/SwaggerConverter.java
@@ -104,7 +104,7 @@ public class SwaggerConverter implements SwaggerParserExtension {
         SwaggerParseResult out = convert(result);
         if (out != null && out.getOpenAPI() != null && options != null) {
             if (options.isResolveFully()) {
-                new ResolverFully(options.isResolveCombinators()).resolveFully(out.getOpenAPI());
+                new ResolverFully(options).resolveFully(out.getOpenAPI());
             }
             if (options.isFlatten()) {
                 try {
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/OpenAPIV3Parser.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/OpenAPIV3Parser.java
index b1a5d844..74e9af63 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/OpenAPIV3Parser.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/OpenAPIV3Parser.java
@@ -227,7 +227,7 @@ public class OpenAPIV3Parser implements SwaggerParserExtension {
                                 dereferencer.dereference(dereferencerContext, dereferencers.iterator());
                             }
                             if (options.isResolveFully()) {
-                                new ResolverFully(options.isResolveCombinators()).resolveFully(result.getOpenAPI());
+                                new ResolverFully(options).resolveFully(result.getOpenAPI());
                             }
                         } else {
                             String msg = "Resolution of OAS 3.1 spec disabled by 'disableOas31Resolve' env variable";
@@ -239,7 +239,7 @@ public class OpenAPIV3Parser implements SwaggerParserExtension {
                                 location, null, options);
                         resolver.resolve(result);
                         if (options.isResolveFully()) {
-                            new ResolverFully(options.isResolveCombinators()).resolveFully(result.getOpenAPI());
+                            new ResolverFully(options).resolveFully(result.getOpenAPI());
                         }
                     }
 
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/ResolverFully.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/ResolverFully.java
index 497260ab..3f3e19ec 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/ResolverFully.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/ResolverFully.java
@@ -15,6 +15,7 @@ import io.swagger.v3.oas.models.parameters.Parameter;
 import io.swagger.v3.oas.models.parameters.RequestBody;
 import io.swagger.v3.oas.models.responses.ApiResponse;
 import io.swagger.v3.oas.models.responses.ApiResponses;
+import io.swagger.v3.parser.core.models.ParseOptions;
 import io.swagger.v3.parser.models.RefFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,7 +39,7 @@ public class ResolverFully {
 
     private boolean aggregateCombinators;
 
-
+    private ParseOptions parseOptions = new ParseOptions();
 
     public ResolverFully() {
         this(true);
@@ -48,6 +49,15 @@ public class ResolverFully {
         this.aggregateCombinators = aggregateCombinators;
     }
 
+    public ResolverFully(ParseOptions options) {
+        if (options != null) {
+            this.aggregateCombinators = options.isResolveCombinators();
+        } else {
+            this.aggregateCombinators = true;
+        }
+        this.parseOptions = options;
+    }
+
     private Map<String, Schema> schemas;
     private Map<String, Schema> resolvedModels = new HashMap<>();
     private Map<String, Example> examples;
@@ -493,7 +503,7 @@ public class ResolverFully {
                 Schema property = updated.get(key);
 
                 if (property.getProperties() != model.getProperties()) {
-                    if (!hasSchemaType(property)) {
+                    if (!hasSchemaType(property) && parseOptions.isExplicitObjectSchema()) {
                         if (SpecVersion.V30.equals(property.getSpecVersion())) {
                             property.setType("object");
                         } else {


Test patch:
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
index 6516c95c..ff1b3530 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
@@ -45,6 +45,7 @@ import org.apache.commons.io.FileUtils;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import java.io.File;
@@ -597,7 +598,7 @@ public class OpenAPIResolverTest {
         options.setResolveFully(true);
 
         OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml,auths,options).getOpenAPI();
-        ResolverFully resolverUtil = new ResolverFully();
+        ResolverFully resolverUtil = new ResolverFully(options);
         resolverUtil.resolveFully(openAPI);
 
 
@@ -643,7 +644,7 @@ public class OpenAPIResolverTest {
         options.setResolveFully(true);
 
         OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml,auths,options).getOpenAPI();
-        ResolverFully resolverUtil = new ResolverFully();
+        ResolverFully resolverUtil = new ResolverFully(options);
         resolverUtil.resolveFully(openAPI);
         Parameter param = openAPI.getPaths().get("/test/method").getPost().getParameters().get(0);
 
@@ -1475,5 +1476,29 @@ public class OpenAPIResolverTest {
         final OpenAPI output = new OpenAPIV3Parser().read(actualLocation, null, options);
         new OpenAPIResolver(output, null, actualLocation).resolve();
     }
+
+    @Test(dataProvider = "explicitObjectSchemaProvider")
+    public void testIssue2113(boolean explicitObjectSchema) {
+        ParseOptions options = new ParseOptions();
+        options.setResolve(true);
+        options.setResolveFully(true);
+        options.setExplicitObjectSchema(explicitObjectSchema);
+
+        OpenAPI openAPI = new OpenAPIV3Parser().readLocation("issue_2113.yaml", auths, options).getOpenAPI();
+        ObjectSchema schema = (ObjectSchema) openAPI.getPaths().get("/foo").getPost().getRequestBody().getContent().get("application/json").getSchema();
+        if (explicitObjectSchema) {
+            assertEquals(schema.getProperties().get("goo").getType(), "object");
+        } else {
+            assertNull(schema.getProperties().get("goo").getType());
+        }
+    }
+
+    @DataProvider(name = "explicitObjectSchemaProvider")
+    public Object[][] explicitObjectSchemaProvider() {
+        return new Object[][] {
+                { true },
+                { false }
+        };
+    }
     
 }
diff --git a/modules/swagger-parser-v3/src/test/resources/issue_2113.yaml b/modules/swagger-parser-v3/src/test/resources/issue_2113.yaml
new file mode 100644
index 00000000..854f1f4f
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue_2113.yaml
@@ -0,0 +1,28 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: Issue X
+servers:
+  - url: http://petstore.swagger.io/api
+paths:
+  /foo:
+    post:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/ObjectType'
+        required: true
+      responses:
+        200:
+          description: ok
+components:
+  schemas:
+    ObjectType:
+      type: object
+      allOf:
+        - $ref: "#/components/schemas/NoType"
+    NoType:
+      properties:
+        goo:
+          title: "Boo"
\ No newline at end of file