From 7329495742e75d8118454f14baa1595aa39afc89 Mon Sep 17 00:00:00 2001 From: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:25:48 +0200 Subject: [PATCH] Upgrade swagger-core from version 2.2.52 to 2.2.53 --- pom.xml | 2 +- .../api/AbstractOpenApiResource.java | 19 -- .../configuration/SpringDocConfiguration.java | 14 -- .../PropertyNamingStrategyConverter.java | 168 ------------------ .../org/springdoc/core/utils/SchemaUtils.java | 26 --- .../springdoc/core/utils/SpringDocUtils.java | 52 ------ .../api/v31/app25/NullKeyController.kt | 2 + .../test/resources/results/3.1.0/app25.json | 48 +++-- 8 files changed, 34 insertions(+), 297 deletions(-) delete mode 100644 springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PropertyNamingStrategyConverter.java diff --git a/pom.xml b/pom.xml index cc36c0655..c350fb3a5 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 0.11.0 1.5.0 - 2.2.52 + 2.2.53 5.32.11 1.13.1 0.9.1 diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java index aa6bbae9e..15dd6d9ba 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java @@ -403,8 +403,6 @@ protected OpenAPI getOpenApi(String serverBaseUrl, Locale locale) { } getPaths(mappingsMap, finalLocale, openAPI); - removeNullKeyComponentProperties(openAPI); - if (springDocConfigProperties.isTrimKotlinIndent()) this.trimIndent(openAPI); @@ -465,23 +463,6 @@ private Locale selectLocale(Locale inputLocale) { return inputLocale == null ? Locale.getDefault() : inputLocale; } - /** - * Removes {@code null}-keyed entries from the component schema properties. Swagger-core - * inserts a {@code null} property key when resolving a {@code @JsonUnwrapped} member whose - * content is a {@code $ref} (for example Spring HATEOAS {@code EntityModel.getContent()} - * with HAL disabled), which otherwise breaks JSON serialization of the document. - * - * @param openAPI the open api - */ - private static void removeNullKeyComponentProperties(OpenAPI openAPI) { - if (openAPI.getComponents() == null || openAPI.getComponents().getSchemas() == null) { - return; - } - for (Schema schema : openAPI.getComponents().getSchemas().values()) { - SpringDocUtils.removeNullKeyProperties(schema); - } - } - /** * Indents are removed for properties that are mainly used as “explanations” using Open API. * diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java index be414ae24..97bc6d08c 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java @@ -56,7 +56,6 @@ import org.springdoc.core.converters.OAS31ModelConverter; import org.springdoc.core.converters.PolymorphicModelConverter; import org.springdoc.core.converters.PropertyCustomizingConverter; -import org.springdoc.core.converters.PropertyNamingStrategyConverter; import org.springdoc.core.converters.ResponseSupportConverter; import org.springdoc.core.converters.SchemaPropertyDeprecatingConverter; import org.springdoc.core.converters.WebFluxSupportConverter; @@ -295,19 +294,6 @@ PolymorphicModelConverter polymorphicModelConverter(ObjectMapperProvider objectM return new PolymorphicModelConverter(objectMapperProvider); } - /** - * Property naming strategy converter property naming strategy converter. - * - * @param objectMapperProvider the object mapper provider - * @return the property naming strategy converter - */ - @Bean - @ConditionalOnMissingBean - @Lazy(false) - PropertyNamingStrategyConverter propertyNamingStrategyConverter(ObjectMapperProvider objectMapperProvider) { - return new PropertyNamingStrategyConverter(objectMapperProvider); - } - /** * Open api builder open api builder. * diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PropertyNamingStrategyConverter.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PropertyNamingStrategyConverter.java deleted file mode 100644 index d711e942c..000000000 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PropertyNamingStrategyConverter.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * - * * - * * * - * * * * - * * * * * - * * * * * * Copyright 2019-2026 the original author or authors. - * * * * * * - * * * * * * Licensed under the Apache License, Version 2.0 (the "License"); - * * * * * * you may not use this file except in compliance with the License. - * * * * * * You may obtain a copy of the License at - * * * * * * - * * * * * * https://www.apache.org/licenses/LICENSE-2.0 - * * * * * * - * * * * * * Unless required by applicable law or agreed to in writing, software - * * * * * * distributed under the License is distributed on an "AS IS" BASIS, - * * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * * * * * See the License for the specific language governing permissions and - * * * * * * limitations under the License. - * * * * - * * * - * * - * - */ - -package org.springdoc.core.converters; - -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.databind.BeanDescription; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; -import io.swagger.v3.core.converter.AnnotatedType; -import io.swagger.v3.core.converter.ModelConverter; -import io.swagger.v3.core.converter.ModelConverterContext; -import io.swagger.v3.core.converter.ModelConverters; -import io.swagger.v3.core.jackson.ModelResolver; -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.media.Schema; -import org.springdoc.core.providers.ObjectMapperProvider; - -/** - * Repairs schema property names that swagger-core leaves in their raw (untranslated) form - * when a Jackson {@code PropertyNamingStrategy} (for example {@code SNAKE_CASE}) is in - * effect. - * - *

- * swagger-core's {@code ModelResolver} carries a long-standing workaround - * (swagger-core#415) - * that overwrites the Jackson-translated property name with the raw member name whenever - * that member name starts with {@code get}/{@code is} followed by a lower-case character. - * For Java records the accessor is named exactly like the component (e.g. - * {@code issuanceDate()}), so a component such as {@code issuanceDate} is clobbered back - * to camelCase while sibling properties like {@code familyName} convert correctly. This - * converter restores the translated name. - *

- * - *

- * The mapper used by the active {@code ModelResolver} is consulted so that the fix is - * self-consistent: it only renames a property when Jackson actually maps its internal - * name to a different external name and swagger-core emitted the internal name. - *

- * - * See: springdoc-openapi#3293 - * - * @author bnasslahsen - */ -public class PropertyNamingStrategyConverter implements ModelConverter { - - /** - * The Spring doc object mapper. - */ - private final ObjectMapperProvider springDocObjectMapper; - - /** - * Instantiates a new Property naming strategy converter. - * @param springDocObjectMapper the spring doc object mapper - */ - public PropertyNamingStrategyConverter(ObjectMapperProvider springDocObjectMapper) { - this.springDocObjectMapper = springDocObjectMapper; - } - - @Override - public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) { - if (!chain.hasNext()) - return null; - Schema resolvedSchema = chain.next().resolve(type, context, chain); - - ObjectMapper mapper = resolverObjectMapper(); - if (mapper == null) - return resolvedSchema; - - Schema targetSchema = resolvedSchema; - if (resolvedSchema != null && resolvedSchema.get$ref() != null) - targetSchema = context.getDefinedModels() - .get(resolvedSchema.get$ref().substring(Components.COMPONENTS_SCHEMAS_REF.length())); - - if (targetSchema == null || targetSchema.getProperties() == null) - return resolvedSchema; - - JavaType javaType = mapper.constructType(type.getType()); - if (javaType == null || javaType.getRawClass().getPackageName().startsWith("java.")) - return resolvedSchema; - - renameClobberedProperties(targetSchema, mapper, javaType); - return resolvedSchema; - } - - /** - * Renames properties that swagger-core emitted under their raw member name back to - * the name Jackson computed from the active naming strategy, preserving property - * order and the required list. - * @param schema the schema to fix - * @param mapper the object mapper used by the active model resolver - * @param javaType the resolved java type - */ - private void renameClobberedProperties(Schema schema, ObjectMapper mapper, JavaType javaType) { - BeanDescription beanDescription; - try { - beanDescription = mapper.getSerializationConfig().introspect(javaType); - } - catch (Exception e) { - return; - } - - Map renames = new LinkedHashMap<>(); - for (BeanPropertyDefinition property : beanDescription.findProperties()) { - String externalName = property.getName(); - String internalName = property.getInternalName(); - if (externalName != null && !externalName.equals(internalName) - && schema.getProperties().containsKey(internalName) - && !schema.getProperties().containsKey(externalName)) { - renames.put(internalName, externalName); - } - } - if (renames.isEmpty()) - return; - - Map renamedProperties = new LinkedHashMap<>(); - schema.getProperties().forEach((name, value) -> renamedProperties.put(renames.getOrDefault(name, name), value)); - schema.setProperties(renamedProperties); - - List required = schema.getRequired(); - if (required != null) - required.replaceAll(name -> renames.getOrDefault(name, name)); - } - - /** - * Returns the {@link ObjectMapper} of the active swagger-core {@code ModelResolver}, - * which is the mapper that applied (or did not apply) the naming strategy while - * generating the schema. - * @return the object mapper, or {@code null} if none could be located - */ - private ObjectMapper resolverObjectMapper() { - for (ModelConverter converter : ModelConverters.getInstance(springDocObjectMapper.isOpenapi31()) - .getConverters()) { - if (converter instanceof ModelResolver modelResolver) - return modelResolver.objectMapper(); - } - return null; - } - -} diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SchemaUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SchemaUtils.java index e4ad206d3..ff7a6d4e5 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SchemaUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SchemaUtils.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; -import io.swagger.v3.oas.models.SpecVersion; import io.swagger.v3.oas.models.media.Schema; import jakarta.validation.OverridesAttribute; import jakarta.validation.constraints.DecimalMax; @@ -307,7 +306,6 @@ else if (OPENAPI_STRING_TYPE.equals(type)) { schema.setMaximum(BigDecimal.valueOf(((Range) anno).max())); } }); - fixOAS31ExclusiveConstraints(schema); if (schema!=null && annotatedNotNull(annotations)) { String specVersion = schema.getSpecVersion().name(); if (!"V30".equals(specVersion)) { @@ -576,28 +574,4 @@ private static JsonProperty getJsonProperty(Field f) { if (g != null) return g.getAnnotation(JsonProperty.class); return null; } - - /** - * Swagger-core 2.2.49 introduced so that {@link Positive} and {@link Negative} are introspected. - * It does not correctly use the fact that exclusiveMinimum/exclusiveMaximum are values in OAS31. - *

- * Tracked under swagger-core#5170. - * - * @param schema the schema to fix - */ - public static void fixOAS31ExclusiveConstraints(Schema schema) { - if (schema == null) { - return; - } - if (schema.getSpecVersion().equals(SpecVersion.V31)) { - if (schema.getExclusiveMaximumValue() != null && schema.getMaximum() != null - && schema.getMaximum().compareTo(schema.getExclusiveMaximumValue()) == 0) { - schema.setMaximum(null); - } - if (schema.getExclusiveMinimumValue() != null && schema.getMinimum() != null && - schema.getMinimum().compareTo(schema.getExclusiveMinimumValue()) == 0) { - schema.setMinimum(null); - } - } - } } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java index ae2fe78e6..594b95229 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocUtils.java @@ -31,7 +31,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.function.Predicate; import com.fasterxml.jackson.core.type.TypeReference; @@ -181,60 +180,9 @@ else if (schema.getItems() != null && schema.getItems().getType() != null if (schema.getProperties() != null) { schema.getProperties().forEach((key, value) -> handleSchemaTypes(value)); } - fixNullOnlyAdditionalProperties(schema); } } - /** - * Fix additionalProperties incorrectly set to {"type": "null"} when @Nullable - * propagates from a Map field to its Object value type (resolved as "any type" = {}). - *

- * Tracked under swagger-core#5115. - * - * @param schema the schema to fix - */ - public static void fixNullOnlyAdditionalProperties(Schema schema) { - if (schema == null) { - return; - } - Object additionalProperties = schema.getAdditionalProperties(); - if (additionalProperties instanceof Schema addPropSchema) { - boolean isNullOnlyType = false; - Set types = addPropSchema.getTypes(); - boolean onlyNullTypeOAS31 = types != null && types.size() == 1 && types.contains("null"); - boolean onlyNullTypeOAS30 = types == null && "null".equals(addPropSchema.getType()); - if (onlyNullTypeOAS31 || onlyNullTypeOAS30) { - isNullOnlyType = true; - } - if (isNullOnlyType && addPropSchema.get$ref() == null - && addPropSchema.getProperties() == null && addPropSchema.getFormat() == null) { - addPropSchema.setTypes(null); - addPropSchema.setType(null); - } - } - if (schema.getProperties() != null) { - schema.getProperties().values().forEach(SpringDocUtils::fixNullOnlyAdditionalProperties); - } - } - - /** - * Removes {@code null}-keyed entries from a schema's properties map (and its nested - * schemas). When swagger-core resolves a {@code @JsonUnwrapped} member (for example - * Spring HATEOAS {@code EntityModel.getContent()} with HAL disabled), the unwrapped - * property schemas may have a {@code null} name and get inserted into the properties map - * under a {@code null} key. Such a key cannot be serialized by Jackson, which fails the - * whole OpenAPI document with {@code "Null key for a Map not allowed in JSON"}. - * - * @param schema the schema to fix - */ - public static void removeNullKeyProperties(Schema schema) { - if (schema == null || schema.getProperties() == null) { - return; - } - schema.getProperties().keySet().removeIf(Objects::isNull); - schema.getProperties().values().forEach(SpringDocUtils::removeNullKeyProperties); - } - /** * Handle schema types. * diff --git a/springdoc-openapi-tests/springdoc-openapi-kotlin-webmvc-tests/src/test/kotlin/test/org/springdoc/api/v31/app25/NullKeyController.kt b/springdoc-openapi-tests/springdoc-openapi-kotlin-webmvc-tests/src/test/kotlin/test/org/springdoc/api/v31/app25/NullKeyController.kt index 6419037a9..f273b1e9e 100644 --- a/springdoc-openapi-tests/springdoc-openapi-kotlin-webmvc-tests/src/test/kotlin/test/org/springdoc/api/v31/app25/NullKeyController.kt +++ b/springdoc-openapi-tests/springdoc-openapi-kotlin-webmvc-tests/src/test/kotlin/test/org/springdoc/api/v31/app25/NullKeyController.kt @@ -8,6 +8,8 @@ import org.springframework.web.bind.annotation.RestController * Reproduces the swagger-core `@JsonUnwrapped` null-property-key issue: when the unwrapped * member resolves to a `$ref` (a named component model), swagger-core adds a `null`-keyed * entry to the enclosing schema properties, which breaks JSON serialization of the document. + * + * Fixed upstream by PR 5193 */ @RestController class NullKeyController { diff --git a/springdoc-openapi-tests/springdoc-openapi-kotlin-webmvc-tests/src/test/resources/results/3.1.0/app25.json b/springdoc-openapi-tests/springdoc-openapi-kotlin-webmvc-tests/src/test/resources/results/3.1.0/app25.json index 650dcae89..148a3c002 100644 --- a/springdoc-openapi-tests/springdoc-openapi-kotlin-webmvc-tests/src/test/resources/results/3.1.0/app25.json +++ b/springdoc-openapi-tests/springdoc-openapi-kotlin-webmvc-tests/src/test/resources/results/3.1.0/app25.json @@ -34,27 +34,41 @@ }, "components": { "schemas": { - "OuterResponse": { - "type": "object", - "properties": { - "contractAddress": { - "type": "string" + "NestedObject" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" }, - "chainType": { - "type": "string", - "enum": [ - "ETH" - ] + "description" : { + "type" : [ "string", "null" ] + } + }, + "required" : [ "name" ] + }, + "OuterResponse" : { + "type" : "object", + "properties" : { + "contractAddress" : { + "type" : "string" + }, + "chainType" : { + "type" : "string", + "enum" : [ "ETH" ] + }, + "nested" : { + "type" : "null", + "oneOf" : [ { + "$ref" : "#/components/schemas/NestedObject" + }, { + "type" : "null" + } ] }, - "success": { - "type": "boolean" + "success" : { + "type" : "boolean" } }, - "required": [ - "chainType", - "contractAddress", - "success" - ] + "required" : [ "chainType", "contractAddress", "success" ] } } }