From d1849dce7cdcf6ff792d68a3e985bbcc91a2a157 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 6 Mar 2026 11:09:58 +0200 Subject: [PATCH 1/3] make schema fields ordered --- .../server/config/SwaggerConfiguration.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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 e33c699311..db2fba022f 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -301,6 +301,25 @@ public class SwaggerConfiguration { schema.setProperties(null); } } + } else if (schema != null && schema.getProperties() != null && !schema.getProperties().isEmpty()) { + try { + var beanDesc = Json.mapper().getSerializationConfig().introspect(javaType); + var orderedNames = beanDesc.findProperties().stream() + .map(p -> p.getName()) + .toList(); + if (!orderedNames.isEmpty()) { + Map current = schema.getProperties(); + var reordered = new LinkedHashMap(); + for (String name : orderedNames) { + Object prop = current.get(name); + if (prop != null) reordered.put(name, prop); + } + current.forEach((k, v) -> reordered.putIfAbsent(k, v)); + schema.setProperties(reordered); + } + } catch (Exception ignored) { + // keep original order if introspection fails + } } } return schema; @@ -627,7 +646,7 @@ public class SwaggerConfiguration { ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))); unauthorizedExamples.put("credentials-expired", errorExample("Expired credentials", - ThingsboardCredentialsExpiredResponse.of("User password expired!", StringUtils.randomAlphanumeric(30)))); + ThingsboardCredentialsExpiredResponse.of("User password expired!", "udgDQOpS1Q4ZFEL8qHF9s8cSKQ7d1h"))); Schema unauthorizedSchema = new Schema<>(); unauthorizedSchema.oneOf(List.of( From a1758b5ddb44dedff73c1c78d323ff55f3b777a9 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 6 Mar 2026 11:28:20 +0200 Subject: [PATCH 2/3] openapi schema fields order fix --- .../server/config/SwaggerConfiguration.java | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 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 db2fba022f..654d89bc8e 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -61,15 +61,18 @@ import org.springframework.http.HttpStatus; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse; import org.thingsboard.server.exception.ThingsboardErrorResponse; import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.auth.rest.LoginResponse; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -303,15 +306,14 @@ public class SwaggerConfiguration { } } else if (schema != null && schema.getProperties() != null && !schema.getProperties().isEmpty()) { try { - var beanDesc = Json.mapper().getSerializationConfig().introspect(javaType); - var orderedNames = beanDesc.findProperties().stream() - .map(p -> p.getName()) - .toList(); + var beanDesc = JacksonUtil.OBJECT_MAPPER.getSerializationConfig().introspect(javaType); + var orderedNames = resolvePropertyOrder(cls, beanDesc); if (!orderedNames.isEmpty()) { + @SuppressWarnings("unchecked") Map current = schema.getProperties(); - var reordered = new LinkedHashMap(); + var reordered = new LinkedHashMap(); for (String name : orderedNames) { - Object prop = current.get(name); + Schema prop = current.get(name); if (prop != null) reordered.put(name, prop); } current.forEach((k, v) -> reordered.putIfAbsent(k, v)); @@ -674,6 +676,37 @@ public class SwaggerConfiguration { return new ApiResponse().description(description).content(content); } + private static List resolvePropertyOrder(Class cls, com.fasterxml.jackson.databind.BeanDescription beanDesc) { + // Map backing field names to their JSON property names (respects @JsonProperty) + Map fieldToJsonName = new LinkedHashMap<>(); + LinkedHashSet getterOnlyNames = new LinkedHashSet<>(); + for (var prop : beanDesc.findProperties()) { + if (prop.getField() != null) { + fieldToJsonName.put(prop.getField().getName(), prop.getName()); + } else { + getterOnlyNames.add(prop.getName()); + } + } + + // Walk class hierarchy (superclass first) to get field declaration order + List> hierarchy = new ArrayList<>(); + for (Class c = cls; c != null && c != Object.class; c = c.getSuperclass()) { + hierarchy.add(0, c); + } + List ordered = new ArrayList<>(); + for (Class c : hierarchy) { + for (Field f : c.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) continue; + String jsonName = fieldToJsonName.get(f.getName()); + if (jsonName != null) ordered.add(jsonName); + } + } + + // Append getter-only properties (no backing field) at the end + ordered.addAll(getterOnlyNames); + return ordered; + } + private static Example errorExample(String summary, ThingsboardErrorResponse example) { var node = (ObjectNode) JacksonUtil.valueToTree(example); node.put("timestamp", 1609459200000L); From 4f37dace977bd605e6a707ff347881cd14ce2a83 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 6 Mar 2026 12:32:46 +0200 Subject: [PATCH 3/3] minor Swagger improvements --- .../org/thingsboard/server/config/SwaggerConfiguration.java | 4 ++-- 1 file changed, 2 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 654d89bc8e..12a1be5713 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -306,7 +306,7 @@ public class SwaggerConfiguration { } } else if (schema != null && schema.getProperties() != null && !schema.getProperties().isEmpty()) { try { - var beanDesc = JacksonUtil.OBJECT_MAPPER.getSerializationConfig().introspect(javaType); + var beanDesc = Json.mapper().getSerializationConfig().introspect(javaType); var orderedNames = resolvePropertyOrder(cls, beanDesc); if (!orderedNames.isEmpty()) { @SuppressWarnings("unchecked") @@ -320,7 +320,7 @@ public class SwaggerConfiguration { schema.setProperties(reordered); } } catch (Exception ignored) { - // keep original order if introspection fails + log.trace("Failed to resolve property order for {}", cls.getName(), ignored); } } }