2828
2929import java .lang .reflect .Type ;
3030import java .util .Iterator ;
31+ import java .util .LinkedHashMap ;
32+ import java .util .List ;
33+ import java .util .Map ;
3134
3235import com .fasterxml .jackson .databind .JavaType ;
3336import io .swagger .v3 .core .converter .AnnotatedType ;
3437import io .swagger .v3 .core .converter .ModelConverter ;
3538import io .swagger .v3 .core .converter .ModelConverterContext ;
39+ import io .swagger .v3 .oas .models .Components ;
3640import io .swagger .v3 .oas .models .media .Schema ;
3741import 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