Skip to content

Commit 0c9fe6f

Browse files
committed
GH-1335: Add JSON schema property order support
Fixes: #1335 #1335 Add support for maintaining JSON property order in generated schemas using @JsonPropertyOrder. Users can now control the order of properties in their JSON schemas by annotating their classes/records with @JsonPropertyOrder. - Add JacksonOption.RESPECT_JSONPROPERTY_ORDER to BeanOutputConverter - Add test to verify schema property ordering - Update documentation with property ordering example
1 parent bf66415 commit 0c9fe6f

File tree

3 files changed

+45
-1
lines changed

3 files changed

+45
-1
lines changed

spring-ai-core/src/main/java/org/springframework/ai/converter/BeanOutputConverter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
* @author Kirk Lund
5555
* @author Josh Long
5656
* @author Sebastien Deleuze
57+
* @author Soby Chacko
5758
*/
5859
public class BeanOutputConverter<T> implements StructuredOutputConverter<T> {
5960

@@ -125,7 +126,8 @@ private BeanOutputConverter(TypeReference<T> typeRef, ObjectMapper objectMapper)
125126
* Generates the JSON schema for the target type.
126127
*/
127128
private void generateSchema() {
128-
JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED);
129+
JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED,
130+
JacksonOption.RESPECT_JSONPROPERTY_ORDER);
129131
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(
130132
com.github.victools.jsonschema.generator.SchemaVersion.DRAFT_2020_12,
131133
com.github.victools.jsonschema.generator.OptionPreset.PLAIN_JSON)

spring-ai-core/src/test/java/org/springframework/ai/converter/BeanOutputConverterTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
package org.springframework.ai.converter;
1818

1919
import java.time.LocalDate;
20+
import java.util.ArrayList;
2021
import java.util.List;
2122

2223
import com.fasterxml.jackson.annotation.JsonProperty;
2324
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
25+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
2426
import com.fasterxml.jackson.databind.DeserializationFeature;
27+
import com.fasterxml.jackson.databind.JsonNode;
2528
import com.fasterxml.jackson.databind.ObjectMapper;
2629
import org.junit.jupiter.api.Nested;
2730
import org.junit.jupiter.api.Test;
@@ -37,6 +40,7 @@
3740
* @author Sebastian Ullrich
3841
* @author Kirk Lund
3942
* @author Christian Tzolov
43+
* @author Soby Chacko
4044
*/
4145
@ExtendWith(MockitoExtension.class)
4246
class BeanOutputConverterTest {
@@ -112,6 +116,15 @@ String getSomeString() {
112116

113117
}
114118

119+
@JsonPropertyOrder({ "string_property", "foo_property", "bar_property" })
120+
record TestClassWithJsonPropertyOrder(
121+
@JsonProperty("string_property") @JsonPropertyDescription("string_property_description") String someString,
122+
123+
@JsonProperty(required = true, value = "foo_property") String foo,
124+
125+
@JsonProperty(required = true, value = "bar_property") String bar) {
126+
}
127+
115128
@Nested
116129
class ConverterTest {
117130

@@ -155,6 +168,20 @@ void convertClassTypeWithJsonAnnotations() {
155168
assertThat(testClass.getSomeString()).isEqualTo("some value");
156169
}
157170

171+
@Test
172+
void verifySchemaPropertyOrder() throws Exception {
173+
var converter = new BeanOutputConverter<>(TestClassWithJsonPropertyOrder.class);
174+
String jsonSchema = converter.getJsonSchema();
175+
176+
ObjectMapper mapper = new ObjectMapper();
177+
JsonNode schemaNode = mapper.readTree(jsonSchema);
178+
179+
List<String> actualOrder = new ArrayList<>();
180+
schemaNode.get("properties").fieldNames().forEachRemaining(actualOrder::add);
181+
182+
assertThat(actualOrder).containsExactly("string_property", "foo_property", "bar_property");
183+
}
184+
158185
@Test
159186
void convertTypeReferenceWithJsonAnnotations() {
160187
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<TestClassWithJsonAnnotations>() {

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,21 @@ Generation generation = chatModel.call(
139139
ActorsFilms actorsFilms = this.beanOutputConverter.convert(this.generation.getOutput().getContent());
140140
----
141141

142+
=== Property Ordering in Generated Schema
143+
144+
The `BeanOutputConverter` supports custom property ordering in the generated JSON schema through the `@JsonPropertyOrder` annotation.
145+
This annotation allows you to specify the exact sequence in which properties should appear in the schema, regardless of their declaration order in the class or record.
146+
147+
For example, to ensure specific ordering of properties in the `ActorsFilms` record:
148+
149+
[source,java]
150+
----
151+
@JsonPropertyOrder({"actor", "movies"})
152+
record ActorsFilms(String actor, List<String> movies) {}
153+
----
154+
155+
This annotation works with both records and regular Java classes.
156+
142157
==== Generic Bean Types
143158

144159
Use the `ParameterizedTypeReference` constructor to specify a more complex target class structure.

0 commit comments

Comments
 (0)