diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ObjectMapperFactory.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ObjectMapperFactory.java
index 2f27d524b5..29283a2cca 100644
--- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ObjectMapperFactory.java
+++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ObjectMapperFactory.java
@@ -35,7 +35,8 @@
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.links.Link;
import io.swagger.v3.oas.models.links.LinkParameter;
-import io.swagger.v3.oas.models.media.DateSchema;import io.swagger.v3.oas.models.media.Encoding;
+import io.swagger.v3.oas.models.media.DateSchema;
+import io.swagger.v3.oas.models.media.Encoding;
import io.swagger.v3.oas.models.media.EncodingProperty;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ReflectionUtils.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ReflectionUtils.java
index 6146baa0a4..af29d54be9 100644
--- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ReflectionUtils.java
+++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ReflectionUtils.java
@@ -16,6 +16,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@@ -223,6 +224,9 @@ public static boolean isConstructorCompatible(Constructor> constructor) {
* excluding Object
class. If the field from child class hides the field from superclass,
* the field from superclass won't be added to the result list.
*
+ * The list is sorted by name to make the output of this method deterministic.
+ * See https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getFields--
+ *
* @param cls is the processing class
* @return list of Fields
*/
@@ -241,6 +245,10 @@ public static List getDeclaredFields(Class> cls) {
fields.add(field);
}
}
+
+ // Make sure the order is deterministic
+ fields.sort(Comparator.comparing(Field::getName));
+
return fields;
}
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ModelSerializerTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ModelSerializerTest.java
index 0f4d0c7be2..b13386269c 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ModelSerializerTest.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ModelSerializerTest.java
@@ -367,4 +367,4 @@ public void testEnumWithNull() throws Exception {
SerializationMatchers.assertEqualsToYaml(model, yaml);
}
-}
\ No newline at end of file
+}
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/util/reflection/ReflectionUtilsTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/reflection/ReflectionUtilsTest.java
index 7192b5217f..a583765a13 100644
--- a/modules/swagger-core/src/test/java/io/swagger/v3/core/util/reflection/ReflectionUtilsTest.java
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/reflection/ReflectionUtilsTest.java
@@ -3,6 +3,7 @@
import io.swagger.v3.core.util.ReflectionUtils;
import io.swagger.v3.core.util.reflection.resources.Child;
import io.swagger.v3.core.util.reflection.resources.IParent;
+import io.swagger.v3.core.util.reflection.resources.ObjectWithManyFields;
import io.swagger.v3.core.util.reflection.resources.Parent;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -11,10 +12,13 @@
import org.testng.annotations.Test;
import javax.ws.rs.Path;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
import static org.testng.Assert.assertNull;
@@ -134,6 +138,14 @@ public void getDeclaredFieldsFromInterfaceTest() throws NoSuchMethodException {
Assert.assertEquals(Collections.emptyList(), ReflectionUtils.getDeclaredFields(cls));
}
+ @Test
+ public void declaredFieldsShouldBeSorted() {
+ final Class cls = ObjectWithManyFields.class;
+ final List declaredFields = ReflectionUtils.getDeclaredFields(cls);
+ Assert.assertEquals(4, declaredFields.size());
+ Assert.assertEquals(Arrays.asList("a", "b", "c", "d"), declaredFields.stream().map(Field::getName).collect(Collectors.toList()));
+ }
+
@Test
public void testFindMethodForNullClass() throws Exception {
Method method = ReflectionUtilsTest.class.getMethod("testFindMethodForNullClass", (Class>[]) null);
diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/util/reflection/resources/ObjectWithManyFields.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/reflection/resources/ObjectWithManyFields.java
new file mode 100644
index 0000000000..cba795cd37
--- /dev/null
+++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/reflection/resources/ObjectWithManyFields.java
@@ -0,0 +1,10 @@
+package io.swagger.v3.core.util.reflection.resources;
+
+public class ObjectWithManyFields {
+
+ public String a;
+ public boolean d;
+ public Integer c;
+ public Object b;
+
+}
diff --git a/modules/swagger-gradle-plugin/README.md b/modules/swagger-gradle-plugin/README.md
index a37074d25a..84dfd6b0be 100644
--- a/modules/swagger-gradle-plugin/README.md
+++ b/modules/swagger-gradle-plugin/README.md
@@ -64,6 +64,7 @@ Parameter | Description | Required | Default
`resourcePackages`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
`resourceClasses`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
`prettyPrint`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`TRUE`
+`sortOutput`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`FALSE`
`openApiFile`|openapi file to be merged with resolved specification, equivalent to [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties) openAPI|false|
`filterClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
`readerClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
@@ -95,3 +96,6 @@ info:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
```
+
+Since version 2.1.6, `sortOutput` parameter is available, allowing to sort object properties and map keys alphabetically.
+Since version 2.1.6, `objectMapperProcessorClass` allows to configure also the ObjectMapper instance used to serialize the resolved OpenAPI
diff --git a/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java b/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java
index 552fdf5529..d78e6ef886 100644
--- a/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java
+++ b/modules/swagger-gradle-plugin/src/main/java/io/swagger/v3/plugins/gradle/tasks/ResolveTask.java
@@ -66,6 +66,8 @@ public enum Format {JSON, YAML, JSONANDYAML};
private LinkedHashSet modelConverterClasses;
private String objectMapperProcessorClass;
+ private Boolean sortOutput = Boolean.FALSE;
+
private String contextId;
@Input
@@ -294,6 +296,17 @@ public void setEncoding(String resourceClasses) {
this.encoding = encoding;
}
+ @Input
+ @Optional
+ public Boolean getSortOutput() {
+ return sortOutput;
+ }
+
+ public void setSortOutput(Boolean sortOutput) {
+ this.sortOutput = sortOutput;
+ }
+
+
@TaskAction
public void resolve() throws GradleException {
if (skip) {
@@ -390,6 +403,9 @@ public void resolve() throws GradleException {
method=swaggerLoaderClass.getDeclaredMethod("setPrettyPrint", Boolean.class);
method.invoke(swaggerLoader, prettyPrint);
+ method=swaggerLoaderClass.getDeclaredMethod("setSortOutput", Boolean.class);
+ method.invoke(swaggerLoader, sortOutput);
+
method=swaggerLoaderClass.getDeclaredMethod("setReadAllResources", Boolean.class);
method.invoke(swaggerLoader, readAllResources);
diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java
index 372940c6e0..9e2127a4c4 100644
--- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java
+++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java
@@ -1,9 +1,20 @@
package io.swagger.v3.oas.integration;
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.jackson.ModelResolver;
+import io.swagger.v3.core.jackson.PathsSerializer;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.oas.integration.api.ObjectMapperProcessor;
import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
import io.swagger.v3.oas.integration.api.OpenApiConfigurationLoader;
@@ -11,6 +22,8 @@
import io.swagger.v3.oas.integration.api.OpenApiReader;
import io.swagger.v3.oas.integration.api.OpenApiScanner;
import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
@@ -43,6 +56,9 @@ public class GenericOpenApiContext implements O
private ObjectMapperProcessor objectMapperProcessor;
private Set modelConverters;
+ private ObjectMapper outputJsonMapper;
+ private ObjectMapper outputYamlMapper;
+
private ConcurrentHashMap cache = new ConcurrentHashMap<>();
// 0 doesn't cache
@@ -210,6 +226,52 @@ public final T modelConverters(Set modelConverters) {
return (T) this;
}
+ /**
+ * @since 2.1.6
+ */
+ public ObjectMapper getOutputJsonMapper() {
+ return outputJsonMapper;
+ }
+
+ /**
+ * @since 2.1.6
+ */
+ @Override
+ public void setOutputJsonMapper(ObjectMapper outputJsonMapper) {
+ this.outputJsonMapper = outputJsonMapper;
+ }
+
+ /**
+ * @since 2.1.6
+ */
+ public final T outputJsonMapper(ObjectMapper outputJsonMapper) {
+ this.outputJsonMapper = outputJsonMapper;
+ return (T) this;
+ }
+
+ /**
+ * @since 2.1.6
+ */
+ public ObjectMapper getOutputYamlMapper() {
+ return outputYamlMapper;
+ }
+
+ /**
+ * @since 2.1.6
+ */
+ @Override
+ public void setOutputYamlMapper(ObjectMapper outputYamlMapper) {
+ this.outputYamlMapper = outputYamlMapper;
+ }
+
+ /**
+ * @since 2.1.6
+ */
+ public final T outputYamlMapper(ObjectMapper outputYamlMapper) {
+ this.outputYamlMapper = outputYamlMapper;
+ return (T) this;
+ }
+
protected void register() {
OpenApiContextLocator.getInstance().putOpenApiContext(id, this);
@@ -363,16 +425,36 @@ public T init() throws OpenApiConfigurationException {
if (modelConverters == null || modelConverters.isEmpty()) {
modelConverters = buildModelConverters(ContextUtils.deepCopy(openApiConfiguration));
}
+ if (outputJsonMapper == null) {
+ outputJsonMapper = Json.mapper().copy();
+ }
+ if (outputYamlMapper == null) {
+ outputYamlMapper = Yaml.mapper().copy();
+ }
+ if (openApiConfiguration.isSortOutput() != null && openApiConfiguration.isSortOutput()) {
+ outputJsonMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
+ outputJsonMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
+ outputYamlMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
+ outputYamlMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
+ outputJsonMapper.addMixIn(OpenAPI.class, SortedOpenAPIMixin.class);
+ outputJsonMapper.addMixIn(Schema.class, SortedSchemaMixin.class);
+ outputYamlMapper.addMixIn(OpenAPI.class, SortedOpenAPIMixin.class);
+ outputYamlMapper.addMixIn(Schema.class, SortedSchemaMixin.class);
+ }
} catch (Exception e) {
LOGGER.error("error initializing context: " + e.getMessage(), e);
throw new OpenApiConfigurationException("error initializing context: " + e.getMessage(), e);
}
+
try {
if (objectMapperProcessor != null) {
ObjectMapper mapper = IntegrationObjectMapperFactory.createJson();
objectMapperProcessor.processJsonObjectMapper(mapper);
ModelConverters.getInstance().addConverter(new ModelResolver(mapper));
+
+ objectMapperProcessor.processOutputJsonObjectMapper(outputJsonMapper);
+ objectMapperProcessor.processOutputYamlObjectMapper(outputYamlMapper);
}
} catch (Exception e) {
LOGGER.error("error configuring objectMapper: " + e.getMessage(), e);
@@ -442,6 +524,9 @@ private OpenAPIConfiguration mergeParentConfiguration(OpenAPIConfiguration confi
if (merged.isPrettyPrint() == null) {
merged.setPrettyPrint(parentConfig.isPrettyPrint());
}
+ if (merged.isSortOutput() == null) {
+ merged.setSortOutput(parentConfig.isSortOutput());
+ }
if (merged.isReadAllResources() == null) {
merged.setReadAllResources(parentConfig.isReadAllResources());
}
@@ -493,4 +578,36 @@ boolean isStale(long cacheTTL) {
}
}
+ @JsonPropertyOrder(value = {"openapi", "info", "externalDocs", "servers", "security", "tags", "paths", "components"}, alphabetic = true)
+ static abstract class SortedOpenAPIMixin {
+
+ @JsonAnyGetter
+ @JsonPropertyOrder(alphabetic = true)
+ public abstract Map getExtensions();
+
+ @JsonAnySetter
+ public abstract void addExtension(String name, Object value);
+
+ @JsonSerialize(using = PathsSerializer.class)
+ public abstract Paths getPaths();
+ }
+
+ @JsonPropertyOrder(value = {"type", "format"}, alphabetic = true)
+ static abstract class SortedSchemaMixin {
+
+ @JsonAnyGetter
+ @JsonPropertyOrder(alphabetic = true)
+ public abstract Map getExtensions();
+
+ @JsonAnySetter
+ public abstract void addExtension(String name, Object value);
+
+ @JsonIgnore
+ public abstract boolean getExampleSetFlag();
+
+ @JsonInclude(JsonInclude.Include.CUSTOM)
+ public abstract Object getExample();
+
+ }
+
}
diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java
index 66433a9ded..7d5751e138 100644
--- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java
+++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java
@@ -30,6 +30,8 @@ public class SwaggerConfiguration implements OpenAPIConfiguration {
private Set modelConverterClasses;
private String objectMapperProcessorClass;
+ private Boolean sortOutput;
+
public Long getCacheTTL() {
return cacheTTL;
}
@@ -231,4 +233,27 @@ public SwaggerConfiguration modelConverterClasses(Set modelConverterClas
this.modelConverterClasses = modelConverterClasses;
return this;
}
+
+ /**
+ * @since 2.1.6
+ */
+ @Override
+ public Boolean isSortOutput() {
+ return sortOutput;
+ }
+
+ /**
+ * @since 2.1.6
+ */
+ public void setSortOutput(Boolean sortOutput) {
+ this.sortOutput = sortOutput;
+ }
+
+ /**
+ * @since 2.1.6
+ */
+ public SwaggerConfiguration sortOutput(Boolean sortOutput) {
+ setSortOutput(sortOutput);
+ return this;
+ }
}
diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/ObjectMapperProcessor.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/ObjectMapperProcessor.java
index a1487a9787..0c25399a21 100644
--- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/ObjectMapperProcessor.java
+++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/ObjectMapperProcessor.java
@@ -7,12 +7,24 @@
*/
public interface ObjectMapperProcessor {
- void processJsonObjectMapper(ObjectMapper mapper);
+ default void processJsonObjectMapper(ObjectMapper mapper) {};
/**
* @deprecated since 2.0.7, as no-op
*
*/
@Deprecated
- void processYamlObjectMapper(ObjectMapper mapper);
+ default void processYamlObjectMapper(ObjectMapper mapper) {}
+
+ /**
+ * @since 2.1.6
+ */
+ default void processOutputJsonObjectMapper(ObjectMapper mapper) {}
+
+ /**
+ * @since 2.1.6
+ */
+ default void processOutputYamlObjectMapper(ObjectMapper mapper) {
+ processOutputJsonObjectMapper(mapper);
+ }
}
diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java
index 844cf6369b..f74568c731 100644
--- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java
+++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenAPIConfiguration.java
@@ -39,4 +39,9 @@ public interface OpenAPIConfiguration {
*/
public Set getModelConverterClasses();
+ /**
+ * @since 2.1.6
+ */
+ Boolean isSortOutput();
+
}
diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenApiContext.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenApiContext.java
index e3c22beabc..31b4dce94a 100644
--- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenApiContext.java
+++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/api/OpenApiContext.java
@@ -1,5 +1,6 @@
package io.swagger.v3.oas.integration.api;
+import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.oas.integration.OpenApiConfigurationException;
import io.swagger.v3.oas.models.OpenAPI;
@@ -38,4 +39,26 @@ public interface OpenApiContext {
*/
void setModelConverters(Set modelConverters);
+
+ /**
+ * @since 2.1.6
+ */
+ ObjectMapper getOutputJsonMapper();
+
+ /**
+ * @since 2.1.6
+ */
+ ObjectMapper getOutputYamlMapper();
+
+
+ /**
+ * @since 2.1.6
+ */
+ void setOutputJsonMapper(ObjectMapper outputJsonMapper);
+
+ /**
+ * @since 2.1.6
+ */
+ void setOutputYamlMapper(ObjectMapper outputYamlMapper);
+
}
diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java
index 7bd29d749b..a2996860f7 100644
--- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java
+++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java
@@ -58,6 +58,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -68,6 +69,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
+import java.util.stream.Collectors;
public class Reader implements OpenApiReader {
private static final Logger LOGGER = LoggerFactory.getLogger(Reader.class);
@@ -373,8 +375,13 @@ public OpenAPI read(Class> cls,
// look for field-level annotated properties
globalParameters.addAll(ReaderUtils.collectFieldParameters(cls, components, classConsumes, null));
+ // Make sure that the class methods are sorted for deterministic order
+ // See https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getMethods--
+ final List methods = Arrays.stream(cls.getMethods())
+ .sorted(new MethodComparator())
+ .collect(Collectors.toList());
+
// iterate class methods
- Method[] methods = cls.getMethods();
for (Method method : methods) {
if (isOperationHidden(method)) {
continue;
@@ -1471,4 +1478,36 @@ private static Class> getClassArgument(Type cls) {
return null;
}
}
+
+ /**
+ * Comparator for uniquely sorting a collection of Method objects.
+ * Supports overloaded methods (with the same name).
+ *
+ * @see Method
+ */
+ private static class MethodComparator implements Comparator {
+
+ @Override
+ public int compare(Method m1, Method m2) {
+ // First compare the names of the method
+ int val = m1.getName().compareTo(m2.getName());
+
+ // If the names are equal, compare each argument type
+ if (val == 0) {
+ val = m1.getParameterTypes().length - m2.getParameterTypes().length;
+ if (val == 0) {
+ Class>[] types1 = m1.getParameterTypes();
+ Class>[] types2 = m2.getParameterTypes();
+ for (int i = 0; i < types1.length; i++) {
+ val = types1[i].getName().compareTo(types2[i].getName());
+
+ if (val != 0) {
+ break;
+ }
+ }
+ }
+ }
+ return val;
+ }
+ }
}
diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/OpenApiServlet.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/OpenApiServlet.java
index ab928b8d5a..40523108ae 100644
--- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/OpenApiServlet.java
+++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/OpenApiServlet.java
@@ -1,9 +1,8 @@
package io.swagger.v3.jaxrs2.integration;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import io.swagger.v3.core.filter.OpenAPISpecFilter;
import io.swagger.v3.core.filter.SpecFilter;
-import io.swagger.v3.core.util.Json;
-import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.jaxrs2.util.ServletUtils;
import io.swagger.v3.oas.integration.OpenApiConfigurationException;
import io.swagger.v3.oas.integration.OpenApiContextLocator;
@@ -90,12 +89,12 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
if (type.equalsIgnoreCase("yaml")) {
resp.setContentType(APPLICATION_YAML);
try (PrintWriter pw = resp.getWriter()) {
- pw.write(pretty ? Yaml.pretty(oas) : Yaml.mapper().writeValueAsString(oas));
+ pw.write(pretty ? ctx.getOutputYamlMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(oas) : ctx.getOutputYamlMapper().writeValueAsString(oas));
}
} else {
resp.setContentType(APPLICATION_JSON);
try (PrintWriter pw = resp.getWriter()) {
- pw.write(pretty ? Json.pretty(oas) : Json.mapper().writeValueAsString(oas));
+ pw.write(pretty ? ctx.getOutputJsonMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(oas) : ctx.getOutputJsonMapper().writeValueAsString(oas));
}
}
diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java
index bdccfc629f..e6320b6650 100644
--- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java
+++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletConfigContextUtils.java
@@ -26,6 +26,11 @@ public class ServletConfigContextUtils {
public static final String OPENAPI_CONFIGURATION_FILTER_KEY = "openApi.configuration.filterClass";
public static final String OPENAPI_CONFIGURATION_CACHE_TTL_KEY = "openApi.configuration.cacheTTL";
+ /**
+ * @since 2.1.6
+ */
+ public static final String OPENAPI_CONFIGURATION_SORTOUTPUT_KEY = "openApi.configuration.sortOutput";
+
/**
* @since 2.0.6
*/
diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java
index 719d700c3e..3c343c56c9 100644
--- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java
+++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/ServletOpenApiConfigurationLoader.java
@@ -21,6 +21,7 @@
import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_READALLRESOURCES_KEY;
import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_READER_KEY;
import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SCANNER_KEY;
+import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.OPENAPI_CONFIGURATION_SORTOUTPUT_KEY;
import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.getBooleanInitParam;
import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.getInitParam;
import static io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils.getLongInitParam;
@@ -53,6 +54,7 @@ public OpenAPIConfiguration load(String path) throws IOException {
.resourceClasses(resolveResourceClasses(servletConfig))
.readAllResources(getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_READALLRESOURCES_KEY))
.prettyPrint(getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_PRETTYPRINT_KEY))
+ .sortOutput(getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_SORTOUTPUT_KEY))
.readerClass(getInitParam(servletConfig, OPENAPI_CONFIGURATION_READER_KEY))
.cacheTTL(getLongInitParam(servletConfig, OPENAPI_CONFIGURATION_CACHE_TTL_KEY))
.scannerClass(getInitParam(servletConfig, OPENAPI_CONFIGURATION_SCANNER_KEY))
@@ -107,6 +109,9 @@ public boolean exists(String path) {
if (getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_PRETTYPRINT_KEY) != null) {
return true;
}
+ if (getBooleanInitParam(servletConfig, OPENAPI_CONFIGURATION_SORTOUTPUT_KEY) != null) {
+ return true;
+ }
if (getInitParam(servletConfig, OPENAPI_CONFIGURATION_READER_KEY) != null) {
return true;
}
diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java
index 5d198f9a1e..8c3e4a4f0b 100644
--- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java
+++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/SwaggerLoader.java
@@ -1,5 +1,6 @@
package io.swagger.v3.jaxrs2.integration;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import io.swagger.v3.core.filter.OpenAPISpecFilter;
import io.swagger.v3.core.filter.SpecFilter;
import io.swagger.v3.core.util.Json;
@@ -7,6 +8,7 @@
import io.swagger.v3.oas.integration.GenericOpenApiContextBuilder;
import io.swagger.v3.oas.integration.OpenApiConfigurationException;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
+import io.swagger.v3.oas.integration.api.OpenApiContext;
import io.swagger.v3.oas.models.OpenAPI;
import org.apache.commons.lang3.StringUtils;
@@ -36,6 +38,8 @@ public class SwaggerLoader {
private String objectMapperProcessorClass;
private String modelConverterClasses;
+ private Boolean sortOutput = false;
+
/**
* @since 2.0.6
*/
@@ -159,6 +163,20 @@ public void setOpenapiAsString(String openapiAsString) {
this.openapiAsString = openapiAsString;
}
+ /**
+ * @since 2.1.6
+ */
+ public Boolean getSortOutput() {
+ return sortOutput;
+ }
+
+ /**
+ * @since 2.1.6
+ */
+ public void setSortOutput(Boolean sortOutput) {
+ this.sortOutput = sortOutput;
+ }
+
public Map resolve() throws Exception{
@@ -204,7 +222,8 @@ public Map resolve() throws Exception{
.resourceClasses(resourceClassesSet)
.resourcePackages(resourcePackagesSet)
.objectMapperProcessorClass(objectMapperProcessorClass)
- .modelConverterClasses(modelConverterSet);
+ .modelConverterClasses(modelConverterSet)
+ .sortOutput(sortOutput);
try {
GenericOpenApiContextBuilder builder = new JaxrsOpenApiContextBuilder()
.openApiConfiguration(config);
@@ -212,9 +231,8 @@ public Map resolve() throws Exception{
builder.ctxId(contextId);
}
- OpenAPI openAPI = builder
- .buildContext(true)
- .read();
+ OpenApiContext context = builder.buildContext(true);
+ OpenAPI openAPI = context.read();
if (StringUtils.isNotBlank(filterClass)) {
try {
OpenAPISpecFilter filterImpl = (OpenAPISpecFilter) this.getClass().getClassLoader().loadClass(filterClass).newInstance();
@@ -230,18 +248,17 @@ public Map resolve() throws Exception{
String openapiYaml = null;
if ("JSON".equals(outputFormat) || "JSONANDYAML".equals(outputFormat)) {
if (prettyPrint != null && prettyPrint) {
- openapiJson = Json.pretty(openAPI);
+ openapiJson = context.getOutputJsonMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(openAPI);
} else {
- openapiJson = Json.mapper().writeValueAsString(openAPI);
+ openapiJson = context.getOutputJsonMapper().writeValueAsString(openAPI);
}
}
if ("YAML".equals(outputFormat) || "JSONANDYAML".equals(outputFormat)) {
if (prettyPrint != null && prettyPrint) {
- openapiYaml = Yaml.pretty(openAPI);
+ openapiYaml = context.getOutputYamlMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(openAPI);
} else {
- openapiYaml = Yaml.mapper().writeValueAsString(openAPI);
+ openapiYaml = context.getOutputYamlMapper().writeValueAsString(openAPI);
}
-
}
Map map = new HashMap<>();
map.put("JSON", openapiJson);
diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/resources/BaseOpenApiResource.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/resources/BaseOpenApiResource.java
index 61e48dfe8f..bb179cf810 100644
--- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/resources/BaseOpenApiResource.java
+++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/integration/resources/BaseOpenApiResource.java
@@ -1,9 +1,8 @@
package io.swagger.v3.jaxrs2.integration.resources;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import io.swagger.v3.core.filter.OpenAPISpecFilter;
import io.swagger.v3.core.filter.SpecFilter;
-import io.swagger.v3.core.util.Json;
-import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContextBuilder;
import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
import io.swagger.v3.oas.integration.api.OpenApiContext;
@@ -74,12 +73,16 @@ protected Response getOpenApi(HttpHeaders headers,
if (StringUtils.isNotBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
return Response.status(Response.Status.OK)
- .entity(pretty ? Yaml.pretty(oas) : Yaml.mapper().writeValueAsString(oas))
+ .entity(pretty ?
+ ctx.getOutputYamlMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(oas) :
+ ctx.getOutputYamlMapper().writeValueAsString(oas))
.type("application/yaml")
.build();
} else {
return Response.status(Response.Status.OK)
- .entity(pretty ? Json.pretty(oas) : Json.mapper().writeValueAsString(oas))
+ .entity(pretty ?
+ ctx.getOutputJsonMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(oas) :
+ ctx.getOutputJsonMapper().writeValueAsString(oas))
.type(MediaType.APPLICATION_JSON_TYPE)
.build();
}
diff --git a/modules/swagger-jaxrs2/src/test/java/com/my/sorted/resources/SortedThing.java b/modules/swagger-jaxrs2/src/test/java/com/my/sorted/resources/SortedThing.java
new file mode 100644
index 0000000000..cd3fa9a2b5
--- /dev/null
+++ b/modules/swagger-jaxrs2/src/test/java/com/my/sorted/resources/SortedThing.java
@@ -0,0 +1,28 @@
+package com.my.sorted.resources;
+
+import io.swagger.v3.jaxrs2.resources.model.Pet;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import java.util.ArrayList;
+
+@Path("/sorted")
+public class SortedThing {
+
+ @Operation(operationId = "foo")
+ @GET
+ @Path("/pet")
+ public Pet foo() {
+ return null;
+ }
+
+ @Operation(operationId = "bar")
+ @GET
+ @Path("/pet")
+ public Pet bar() {
+ return null;
+ }
+
+}
diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/integration/SortedOutputTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/integration/SortedOutputTest.java
new file mode 100644
index 0000000000..2bf08c51dd
--- /dev/null
+++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/integration/SortedOutputTest.java
@@ -0,0 +1,300 @@
+package io.swagger.v3.jaxrs2.integration;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.swagger.v3.core.jackson.PathsSerializer;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.integration.GenericOpenApiContext;
+import io.swagger.v3.oas.integration.SwaggerConfiguration;
+import io.swagger.v3.oas.integration.api.ObjectMapperProcessor;
+import io.swagger.v3.oas.integration.api.OpenApiContext;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.media.Schema;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Application;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import static org.testng.Assert.assertEquals;
+
+public class SortedOutputTest {
+ private JaxrsApplicationAndAnnotationScanner scanner;
+
+ @Path("/app")
+ protected static class ResourceInApplication {
+ @Operation(operationId = "test.")
+ @GET
+ public void getTest(@Parameter(name = "test") ArrayList tenantId) {
+ return;
+ }
+ }
+
+ @BeforeMethod
+ public void setUp() {
+ scanner = new JaxrsApplicationAndAnnotationScanner();
+
+ scanner.setApplication(new Application() {
+ @Override
+ public Set> getClasses() {
+ return Collections.singleton(ResourceInApplication.class);
+ }
+ });
+ }
+
+
+ @Test(description = "sort output test")
+ public void sortOutputTest() throws Exception {
+
+
+ SwaggerConfiguration openApiConfiguration = new SwaggerConfiguration()
+ .sortOutput(true)
+ .resourcePackages(Collections.singleton("com.my.sorted.resources"));
+
+ OpenApiContext ctx = new JaxrsOpenApiContext<>()
+ .openApiConfiguration(openApiConfiguration)
+ .init();
+
+ OpenAPI openApi = ctx.read();
+ String sorted = ctx.getOutputYamlMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(openApi);
+
+ openApiConfiguration = new SwaggerConfiguration()
+ .resourcePackages(Collections.singleton("com.my.sorted.resources"));
+
+ ctx = new JaxrsOpenApiContext<>()
+ .openApiConfiguration(openApiConfiguration)
+ .init();
+
+ String notSorted = ctx.getOutputYamlMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(openApi);
+
+ assertEquals(sorted, expectedSorted);
+ assertEquals(notSorted, expectedNotSorted);
+
+ }
+
+ @JsonPropertyOrder(value = {"openapi", "info", "externalDocs", "servers", "security", "tags", "paths", "components"}, alphabetic = true)
+ public static abstract class SortedOpenAPIMixin {
+
+ @JsonAnyGetter
+ @JsonPropertyOrder(alphabetic = true)
+ public abstract Map getExtensions();
+
+ @JsonAnySetter
+ public abstract void addExtension(String name, Object value);
+
+ @JsonSerialize(using = PathsSerializer.class)
+ public abstract Paths getPaths();
+ }
+
+ @JsonPropertyOrder(value = {"type", "format"}, alphabetic = true)
+ public static abstract class SortedSchemaMixin {
+
+ @JsonAnyGetter
+ @JsonPropertyOrder(alphabetic = true)
+ public abstract Map getExtensions();
+
+ @JsonAnySetter
+ public abstract void addExtension(String name, Object value);
+
+ @JsonIgnore
+ public abstract boolean getExampleSetFlag();
+
+ @JsonInclude(JsonInclude.Include.CUSTOM)
+ public abstract Object getExample();
+
+ }
+
+ public static class SortedProcessor implements ObjectMapperProcessor {
+
+ @Override
+ public void processOutputJsonObjectMapper(ObjectMapper mapper) {
+ mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
+ mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
+ mapper.addMixIn(OpenAPI.class, SortedOpenAPIMixin.class);
+ mapper.addMixIn(Schema.class, SortedSchemaMixin.class);
+ }
+
+ @Override
+ public void processOutputYamlObjectMapper(ObjectMapper mapper) {
+ mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
+ mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
+ mapper.addMixIn(OpenAPI.class, SortedOpenAPIMixin.class);
+ mapper.addMixIn(Schema.class, SortedSchemaMixin.class);
+ }
+ }
+
+ @Test(description = "config output test")
+ public void configOutputTest() throws Exception {
+ SwaggerConfiguration openApiConfiguration = new SwaggerConfiguration()
+ .objectMapperProcessorClass(SortedProcessor.class.getName())
+ .resourcePackages(Collections.singleton("com.my.sorted.resources"));
+
+ OpenApiContext ctx = new JaxrsOpenApiContext<>()
+ .openApiConfiguration(openApiConfiguration)
+ .init();
+
+ OpenAPI openApi = ctx.read();
+ String sorted = ctx.getOutputYamlMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(openApi);
+
+ openApiConfiguration = new SwaggerConfiguration()
+ .resourcePackages(Collections.singleton("com.my.sorted.resources"));
+
+ ctx = new JaxrsOpenApiContext<>()
+ .openApiConfiguration(openApiConfiguration)
+ .init();
+
+ String notSorted = ctx.getOutputYamlMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(openApi);
+
+ assertEquals(sorted, expectedSorted);
+ assertEquals(notSorted, expectedNotSorted);
+
+ }
+ String expectedSorted = "openapi: 3.0.1\n" +
+ "paths:\n" +
+ " /sorted/pet:\n" +
+ " get:\n" +
+ " operationId: foo\n" +
+ " responses:\n" +
+ " default:\n" +
+ " content:\n" +
+ " '*/*':\n" +
+ " schema:\n" +
+ " $ref: '#/components/schemas/Pet'\n" +
+ " description: default response\n" +
+ "components:\n" +
+ " schemas:\n" +
+ " Category:\n" +
+ " type: object\n" +
+ " properties:\n" +
+ " id:\n" +
+ " type: integer\n" +
+ " format: int64\n" +
+ " name:\n" +
+ " type: string\n" +
+ " xml:\n" +
+ " name: Category\n" +
+ " Pet:\n" +
+ " type: object\n" +
+ " properties:\n" +
+ " category:\n" +
+ " $ref: '#/components/schemas/Category'\n" +
+ " id:\n" +
+ " type: integer\n" +
+ " format: int64\n" +
+ " name:\n" +
+ " type: string\n" +
+ " photoUrls:\n" +
+ " type: array\n" +
+ " items:\n" +
+ " type: string\n" +
+ " xml:\n" +
+ " name: photoUrl\n" +
+ " xml:\n" +
+ " wrapped: true\n" +
+ " status:\n" +
+ " type: string\n" +
+ " description: pet status in the store\n" +
+ " enum:\n" +
+ " - \"available,pending,sold\"\n" +
+ " tags:\n" +
+ " type: array\n" +
+ " items:\n" +
+ " $ref: '#/components/schemas/Tag'\n" +
+ " xml:\n" +
+ " wrapped: true\n" +
+ " xml:\n" +
+ " name: Pet\n" +
+ " Tag:\n" +
+ " type: object\n" +
+ " properties:\n" +
+ " id:\n" +
+ " type: integer\n" +
+ " format: int64\n" +
+ " name:\n" +
+ " type: string\n" +
+ " xml:\n" +
+ " name: Tag\n";
+
+ String expectedNotSorted = "openapi: 3.0.1\n" +
+ "paths:\n" +
+ " /sorted/pet:\n" +
+ " get:\n" +
+ " operationId: foo\n" +
+ " responses:\n" +
+ " default:\n" +
+ " description: default response\n" +
+ " content:\n" +
+ " '*/*':\n" +
+ " schema:\n" +
+ " $ref: '#/components/schemas/Pet'\n" +
+ "components:\n" +
+ " schemas:\n" +
+ " Category:\n" +
+ " type: object\n" +
+ " properties:\n" +
+ " id:\n" +
+ " type: integer\n" +
+ " format: int64\n" +
+ " name:\n" +
+ " type: string\n" +
+ " xml:\n" +
+ " name: Category\n" +
+ " Pet:\n" +
+ " type: object\n" +
+ " properties:\n" +
+ " id:\n" +
+ " type: integer\n" +
+ " format: int64\n" +
+ " category:\n" +
+ " $ref: '#/components/schemas/Category'\n" +
+ " name:\n" +
+ " type: string\n" +
+ " photoUrls:\n" +
+ " type: array\n" +
+ " xml:\n" +
+ " wrapped: true\n" +
+ " items:\n" +
+ " type: string\n" +
+ " xml:\n" +
+ " name: photoUrl\n" +
+ " tags:\n" +
+ " type: array\n" +
+ " xml:\n" +
+ " wrapped: true\n" +
+ " items:\n" +
+ " $ref: '#/components/schemas/Tag'\n" +
+ " status:\n" +
+ " type: string\n" +
+ " description: pet status in the store\n" +
+ " enum:\n" +
+ " - \"available,pending,sold\"\n" +
+ " xml:\n" +
+ " name: Pet\n" +
+ " Tag:\n" +
+ " type: object\n" +
+ " properties:\n" +
+ " id:\n" +
+ " type: integer\n" +
+ " format: int64\n" +
+ " name:\n" +
+ " type: string\n" +
+ " xml:\n" +
+ " name: Tag\n";
+
+}
diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/JsonIdentityCyclicResource.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/JsonIdentityCyclicResource.java
index bd20ba044a..79c11c038a 100644
--- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/JsonIdentityCyclicResource.java
+++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/JsonIdentityCyclicResource.java
@@ -19,4 +19,4 @@ public Response test(
@Parameter(required = true) ModelWithJsonIdentityCyclic model) {
return Response.ok().entity("SUCCESS").build();
}
-}
\ No newline at end of file
+}
diff --git a/modules/swagger-maven-plugin/README.md b/modules/swagger-maven-plugin/README.md
index 4b382f4bbe..cf9d8eadfb 100644
--- a/modules/swagger-maven-plugin/README.md
+++ b/modules/swagger-maven-plugin/README.md
@@ -96,6 +96,7 @@ Parameter | Description | Required | Default
`resourcePackages`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
`resourceClasses`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
`prettyPrint`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`TRUE`
+`sortOutput`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`FALSE`
`openapiFilePath`|path to openapi file to be merged with resolved specification, see [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
`configurationFilePath`|path to swagger config file to be merged with resolved specification, see [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration)|false|
`filterClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
@@ -109,4 +110,7 @@ Parameter | Description | Required | Default
***
-Since version 2.0.8, configurationFilePath parameter is available, allowing to specify a path to a [swagger configuration file](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration); If single maven configuration parameters (e.g. `prettyPrint`) are also defined, these will overwrite any value set in configuration file; the same applies to `openapiFilePath` which takes precedence over `openAPI` field in configuration file.
+Since version 2.0.8, `configurationFilePath` parameter is available, allowing to specify a path to a [swagger configuration file](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration); If single maven configuration parameters (e.g. `prettyPrint`) are also defined, these will overwrite any value set in configuration file; the same applies to `openapiFilePath` which takes precedence over `openAPI` field in configuration file.
+
+Since version 2.1.6, `sortOutput` parameter is available, allowing to sort object properties and map keys alphabetically.
+Since version 2.1.6, `objectMapperProcessorClass` allows to configure also the ObjectMapper instance used to serialize the resolved OpenAPI
diff --git a/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java b/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java
index fb6076bb56..e2668668be 100644
--- a/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java
+++ b/modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java
@@ -1,5 +1,6 @@
package io.swagger.v3.plugin.maven;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import io.swagger.v3.core.filter.OpenAPISpecFilter;
import io.swagger.v3.core.filter.SpecFilter;
import io.swagger.v3.core.util.Json;
@@ -8,6 +9,7 @@
import io.swagger.v3.oas.integration.GenericOpenApiContextBuilder;
import io.swagger.v3.oas.integration.OpenApiConfigurationException;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
+import io.swagger.v3.oas.integration.api.OpenApiContext;
import io.swagger.v3.oas.models.OpenAPI;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
@@ -78,9 +80,8 @@ public void execute() throws MojoExecutionException, MojoFailureException {
if (StringUtils.isNotBlank(contextId)) {
builder.ctxId(contextId);
}
- OpenAPI openAPI = builder
- .buildContext(true)
- .read();
+ OpenApiContext context = builder.buildContext(true);
+ OpenAPI openAPI = context.read();
if (StringUtils.isNotBlank(filterClass)) {
try {
@@ -97,20 +98,18 @@ public void execute() throws MojoExecutionException, MojoFailureException {
String openapiJson = null;
String openapiYaml = null;
if (Format.JSON.equals(outputFormat) || Format.JSONANDYAML.equals(outputFormat)) {
- if (prettyPrint) {
- openapiJson = Json.pretty(openAPI);
+ if (prettyPrint != null && prettyPrint) {
+ openapiJson = context.getOutputJsonMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(openAPI);
} else {
- openapiJson = Json.mapper().writeValueAsString(openAPI);
+ openapiJson = context.getOutputJsonMapper().writeValueAsString(openAPI);
}
-
}
if (Format.YAML.equals(outputFormat) || Format.JSONANDYAML.equals(outputFormat)) {
- if (prettyPrint) {
- openapiYaml = Yaml.pretty(openAPI);
+ if (prettyPrint != null && prettyPrint) {
+ openapiYaml = context.getOutputYamlMapper().writer(new DefaultPrettyPrinter()).writeValueAsString(openAPI);
} else {
- openapiYaml = Yaml.mapper().writeValueAsString(openAPI);
+ openapiYaml = context.getOutputYamlMapper().writeValueAsString(openAPI);
}
-
}
Path path = Paths.get(outputPath, "temp");
final File parentFile = path.toFile().getParentFile();
@@ -147,12 +146,18 @@ private void setDefaultsIfMissing(SwaggerConfiguration config) {
if (readAllResources == null) {
readAllResources = Boolean.TRUE;
}
+ if (sortOutput == null) {
+ sortOutput = Boolean.FALSE;
+ }
if (config.isPrettyPrint() == null) {
config.prettyPrint(prettyPrint);
}
if (config.isReadAllResources() == null) {
config.readAllResources(readAllResources);
}
+ if (config.isSortOutput() == null) {
+ config.sortOutput(sortOutput);
+ }
}
/**
@@ -273,6 +278,9 @@ private SwaggerConfiguration mergeConfig(OpenAPI openAPIInput, SwaggerConfigurat
if (prettyPrint != null) {
config.prettyPrint(prettyPrint);
}
+ if (sortOutput != null) {
+ config.sortOutput(sortOutput);
+ }
if (readAllResources != null) {
config.readAllResources(readAllResources);
}
@@ -361,6 +369,12 @@ private boolean isCollectionNotBlank(Collection> collection) {
@Parameter( property = "resolve.encoding" )
private String encoding;
+ /**
+ * @since 2.1.6
+ */
+ @Parameter(property = "resolve.sortOutput")
+ private Boolean sortOutput;
+
private String projectEncoding = "UTF-8";
private SwaggerConfiguration config;