From 07f410d6050e1624920f351b2d88ec00bb4c596f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 18:48:42 +0200 Subject: [PATCH] SwaggerConfiguration improvement: making schema order stable --- .../server/config/SwaggerConfiguration.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 248d43e588..26a0e40f05 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -357,8 +357,12 @@ public class SwaggerConfiguration { allOfElement.setProperties(reordered); } } - } catch (Exception ignored) { - log.trace("Failed to resolve property order for {}", cls.getName(), ignored); + } catch (Exception e) { + log.debug("Failed to resolve property order for {}: {}", cls.getName(), e.getMessage()); + // Fallback: at minimum sort alphabetically for determinism + if (hasProps) { + schema.setProperties(new LinkedHashMap<>(new TreeMap<>(schema.getProperties()))); + } } } } @@ -473,6 +477,12 @@ public class SwaggerConfiguration { .forEach(response -> replaceInlineOneOfInContent(response.getContent(), schemas)); } }); + + // Final safety net: ensure all schema properties are in deterministic order. + // The ModelConverter sorts properties during resolution, but springdoc may + // modify schemas afterwards (e.g. polymorphism handling, discriminator injection). + // This pass catches any properties that were added/reordered post-converter. + schemas.values().forEach(this::ensureDeterministicPropertyOrder); } // Set JsonNode schema last so model scanning cannot overwrite it @@ -824,6 +834,28 @@ public class SwaggerConfiguration { } } + /** + * Ensures all properties in a schema (top-level and inside allOf inline elements) + * are in deterministic alphabetical order. Acts as a safety net for schemas that + * were modified after the ModelConverter's sorting pass (e.g. by springdoc's + * polymorphism handling or discriminator injection for interfaces). + */ + @SuppressWarnings("unchecked") + private void ensureDeterministicPropertyOrder(Schema schema) { + if (schema.getProperties() != null && schema.getProperties().size() > 1) { + schema.setProperties(new LinkedHashMap<>(new TreeMap<>(schema.getProperties()))); + } + if (schema.getAllOf() != null) { + for (Schema allOfElement : (List) schema.getAllOf()) { + if (allOfElement.get$ref() == null + && allOfElement.getProperties() != null + && allOfElement.getProperties().size() > 1) { + allOfElement.setProperties(new LinkedHashMap<>(new TreeMap<>(allOfElement.getProperties()))); + } + } + } + } + /** * Returns the JSON property names that are backed by fields declared directly in {@code cls} * (not inherited from a superclass). Used to distinguish "own" from "inherited" properties