Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
* @author Kirk Lund
* @author Josh Long
* @author Sebastien Deleuze
* @author Soby Chacko
*/
public class BeanOutputConverter<T> implements StructuredOutputConverter<T> {

Expand Down Expand Up @@ -125,7 +126,8 @@ private BeanOutputConverter(TypeReference<T> typeRef, ObjectMapper objectMapper)
* 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +40,7 @@
* @author Sebastian Ullrich
* @author Kirk Lund
* @author Christian Tzolov
* @author Soby Chacko
*/
@ExtendWith(MockitoExtension.class)
class BeanOutputConverterTest {
Expand Down Expand Up @@ -112,6 +116,15 @@ String getSomeString() {

}

@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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a validation, we could perhaps change the order here to make sure the actual order from "JsonPropertyOrder" dictates the ordering. will change it when merging.


@JsonProperty(required = true, value = "bar_property") String bar) {
}

@Nested
class ConverterTest {

Expand Down Expand Up @@ -155,6 +168,20 @@ void convertClassTypeWithJsonAnnotations() {
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>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down