Skip to content

Commit bb797de

Browse files
committed
Stabilize Spring Data Page schema property order
1 parent da4cb67 commit bb797de

1 file changed

Lines changed: 69 additions & 3 deletions

File tree

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PageOpenAPIConverter.java

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@
2828

2929
import java.lang.reflect.Type;
3030
import java.util.Iterator;
31+
import java.util.LinkedHashMap;
32+
import java.util.List;
33+
import java.util.Map;
3134

3235
import com.fasterxml.jackson.databind.JavaType;
3336
import io.swagger.v3.core.converter.AnnotatedType;
3437
import io.swagger.v3.core.converter.ModelConverter;
3538
import io.swagger.v3.core.converter.ModelConverterContext;
39+
import io.swagger.v3.oas.models.Components;
3640
import io.swagger.v3.oas.models.media.Schema;
3741
import org.springdoc.core.providers.ObjectMapperProvider;
3842

@@ -58,6 +62,23 @@ public class PageOpenAPIConverter implements ModelConverter {
5862
*/
5963
private static final AnnotatedType PAGED_MODEL = new AnnotatedType(PagedModel.class).resolveAsRef(true);
6064

65+
/**
66+
* The standard page response property order.
67+
*/
68+
private static final List<String> PAGE_PROPERTY_ORDER = List.of(
69+
"totalPages",
70+
"totalElements",
71+
"size",
72+
"content",
73+
"number",
74+
"sort",
75+
"pageable",
76+
"numberOfElements",
77+
"first",
78+
"last",
79+
"empty"
80+
);
81+
6182
/**
6283
* The Spring doc object mapper.
6384
*/
@@ -90,16 +111,22 @@ public PageOpenAPIConverter(boolean replacePageWithPagedModel, ObjectMapperProvi
90111
@Override
91112
public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
92113
JavaType javaType = springDocObjectMapper.jsonMapper().constructType(type.getType());
114+
boolean isPageType = false;
93115
if (javaType != null) {
94116
Class<?> cls = javaType.getRawClass();
95-
if (replacePageWithPagedModel && PAGE_TO_REPLACE.equals(cls.getCanonicalName())) {
117+
isPageType = PAGE_TO_REPLACE.equals(cls.getCanonicalName());
118+
if (replacePageWithPagedModel && isPageType) {
96119
if (!type.isSchemaProperty())
97120
type = resolvePagedModelType(javaType, type);
98121
else
99122
type.name(getParentTypeName(type, cls));
100123
}
101124
}
102-
return (chain.hasNext()) ? chain.next().resolve(type, context, chain) : null;
125+
Schema schema = (chain.hasNext()) ? chain.next().resolve(type, context, chain) : null;
126+
127+
if (isPageType && !replacePageWithPagedModel)
128+
sortPageSchemaProperties(schema, context);
129+
return schema;
103130
}
104131

105132
/**
@@ -123,4 +150,43 @@ private AnnotatedType resolvePagedModelType(JavaType type, AnnotatedType origina
123150
}
124151
}
125152

126-
}
153+
/**
154+
* Sort page schema properties.
155+
*
156+
* @param schema the schema
157+
* @param context the context
158+
*/
159+
private void sortPageSchemaProperties(Schema schema, ModelConverterContext context) {
160+
Schema pageSchema = resolveReferencedSchema(schema, context);
161+
if (pageSchema == null || pageSchema.getProperties() == null)
162+
return;
163+
164+
Map<String, Schema> properties = pageSchema.getProperties();
165+
if (!properties.keySet().containsAll(PAGE_PROPERTY_ORDER))
166+
return;
167+
168+
Map<String, Schema> sortedProperties = new LinkedHashMap<>();
169+
PAGE_PROPERTY_ORDER.forEach(property -> sortedProperties.put(property, properties.get(property)));
170+
properties.forEach(sortedProperties::putIfAbsent);
171+
pageSchema.setProperties(sortedProperties);
172+
}
173+
174+
/**
175+
* Resolve referenced schema.
176+
*
177+
* @param schema the schema
178+
* @param context the context
179+
* @return the schema
180+
*/
181+
private Schema resolveReferencedSchema(Schema schema, ModelConverterContext context) {
182+
if (schema == null || schema.get$ref() == null)
183+
return schema;
184+
185+
String ref = schema.get$ref();
186+
if (!ref.startsWith(Components.COMPONENTS_SCHEMAS_REF))
187+
return schema;
188+
189+
return context.getDefinedModels().get(ref.substring(Components.COMPONENTS_SCHEMAS_REF.length()));
190+
}
191+
192+
}

0 commit comments

Comments
 (0)