GH-1335: Add JSON schema property order support
Fixes: #1335 https://github.com/spring-projects/spring-ai/issues/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
This commit is contained in:
committed by
Ilayaperumal Gopinathan
parent
9fa89a2fd6
commit
79266be970
@@ -54,6 +54,7 @@ import org.springframework.lang.NonNull;
|
||||
* @author Kirk Lund
|
||||
* @author Josh Long
|
||||
* @author Sebastien Deleuze
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
public class BeanOutputConverter<T> implements StructuredOutputConverter<T> {
|
||||
|
||||
@@ -125,7 +126,8 @@ public class BeanOutputConverter<T> implements StructuredOutputConverter<T> {
|
||||
* Generates the JSON schema for the target type.
|
||||
*/
|
||||
private void generateSchema() {
|
||||
JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED);
|
||||
JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECT_JSONPROPERTY_REQUIRED,
|
||||
JacksonOption.RESPECT_JSONPROPERTY_ORDER);
|
||||
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(
|
||||
com.github.victools.jsonschema.generator.SchemaVersion.DRAFT_2020_12,
|
||||
com.github.victools.jsonschema.generator.OptionPreset.PLAIN_JSON)
|
||||
|
||||
@@ -17,11 +17,14 @@
|
||||
package org.springframework.ai.converter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -37,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Sebastian Ullrich
|
||||
* @author Kirk Lund
|
||||
* @author Christian Tzolov
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BeanOutputConverterTest {
|
||||
@@ -112,6 +116,15 @@ class BeanOutputConverterTest {
|
||||
|
||||
}
|
||||
|
||||
@JsonPropertyOrder({ "string_property", "foo_property", "bar_property" })
|
||||
record TestClassWithJsonPropertyOrder(
|
||||
@JsonProperty("string_property") @JsonPropertyDescription("string_property_description") String someString,
|
||||
|
||||
@JsonProperty(required = true, value = "foo_property") String foo,
|
||||
|
||||
@JsonProperty(required = true, value = "bar_property") String bar) {
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ConverterTest {
|
||||
|
||||
@@ -155,6 +168,20 @@ class BeanOutputConverterTest {
|
||||
assertThat(testClass.getSomeString()).isEqualTo("some value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifySchemaPropertyOrder() throws Exception {
|
||||
var converter = new BeanOutputConverter<>(TestClassWithJsonPropertyOrder.class);
|
||||
String jsonSchema = converter.getJsonSchema();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode schemaNode = mapper.readTree(jsonSchema);
|
||||
|
||||
List<String> actualOrder = new ArrayList<>();
|
||||
schemaNode.get("properties").fieldNames().forEachRemaining(actualOrder::add);
|
||||
|
||||
assertThat(actualOrder).containsExactly("string_property", "foo_property", "bar_property");
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertTypeReferenceWithJsonAnnotations() {
|
||||
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<TestClassWithJsonAnnotations>() {
|
||||
|
||||
@@ -139,6 +139,21 @@ Generation generation = chatModel.call(
|
||||
ActorsFilms actorsFilms = this.beanOutputConverter.convert(this.generation.getOutput().getContent());
|
||||
----
|
||||
|
||||
=== Property Ordering in Generated Schema
|
||||
|
||||
The `BeanOutputConverter` supports custom property ordering in the generated JSON schema through the `@JsonPropertyOrder` annotation.
|
||||
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.
|
||||
|
||||
For example, to ensure specific ordering of properties in the `ActorsFilms` record:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@JsonPropertyOrder({"actor", "movies"})
|
||||
record ActorsFilms(String actor, List<String> movies) {}
|
||||
----
|
||||
|
||||
This annotation works with both records and regular Java classes.
|
||||
|
||||
==== Generic Bean Types
|
||||
|
||||
Use the `ParameterizedTypeReference` constructor to specify a more complex target class structure.
|
||||
|
||||
Reference in New Issue
Block a user