Skip to content

Commit 88552c5

Browse files
committed
Merge branch 'main' into jackson
# Conflicts: # pom.xml
2 parents 4ffc7ac + b7a86cb commit 88552c5

File tree

4 files changed

+183
-1
lines changed

4 files changed

+183
-1
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>org.springframework.ai</groupId>
8+
<artifactId>spring-ai-parent</artifactId>
9+
<version>2.0.0-SNAPSHOT</version>
10+
<relativePath>../../../pom.xml</relativePath>
11+
</parent>
12+
<artifactId>spring-ai-autoconfigure-json</artifactId>
13+
<packaging>jar</packaging>
14+
<name>Spring AI JSON Auto Configuration</name>
15+
<description>Spring AI JSON Auto Configuration</description>
16+
<url>https://github.com/spring-projects/spring-ai</url>
17+
18+
<scm>
19+
<url>https://github.com/spring-projects/spring-ai</url>
20+
<connection>git://github.com/spring-projects/spring-ai.git</connection>
21+
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
22+
</scm>
23+
24+
25+
<dependencies>
26+
27+
<dependency>
28+
<groupId>org.springframework.ai</groupId>
29+
<artifactId>spring-ai-model</artifactId>
30+
<version>${project.parent.version}</version>
31+
</dependency>
32+
33+
<!-- Boot dependencies -->
34+
<dependency>
35+
<groupId>org.springframework.boot</groupId>
36+
<artifactId>spring-boot-starter</artifactId>
37+
</dependency>
38+
39+
<dependency>
40+
<groupId>org.springframework.boot</groupId>
41+
<artifactId>spring-boot-autoconfigure-processor</artifactId>
42+
<optional>true</optional>
43+
</dependency>
44+
45+
<!-- Jackson dependencies -->
46+
<dependency>
47+
<groupId>com.fasterxml.jackson.core</groupId>
48+
<artifactId>jackson-databind</artifactId>
49+
</dependency>
50+
51+
<!-- Test dependencies -->
52+
<dependency>
53+
<groupId>org.springframework.ai</groupId>
54+
<artifactId>spring-ai-test</artifactId>
55+
<version>${project.parent.version}</version>
56+
<scope>test</scope>
57+
</dependency>
58+
59+
<dependency>
60+
<groupId>org.springframework.boot</groupId>
61+
<artifactId>spring-boot-starter-test</artifactId>
62+
<scope>test</scope>
63+
</dependency>
64+
</dependencies>
65+
66+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.autoconfigure.json;
18+
19+
import com.fasterxml.jackson.core.json.JsonReadFeature;
20+
import com.fasterxml.jackson.databind.DeserializationFeature;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.SerializationFeature;
23+
import com.fasterxml.jackson.databind.cfg.CoercionAction;
24+
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
25+
import com.fasterxml.jackson.databind.json.JsonMapper;
26+
27+
import org.springframework.ai.model.ModelOptionsUtils;
28+
import org.springframework.ai.util.JacksonUtils;
29+
import org.springframework.ai.util.json.JsonParser;
30+
import org.springframework.boot.autoconfigure.AutoConfiguration;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
33+
import org.springframework.context.annotation.Bean;
34+
35+
/**
36+
* {@link AutoConfiguration Auto-configuration} for Spring AI JSON parsing. Provides
37+
* customizable ObjectMapper beans for JSON parsing and model options handling. Users can
38+
* override these beans to customize JSON processing behavior.
39+
*
40+
* @author Daniel Albuquerque
41+
* @since 2.0.0
42+
*/
43+
@AutoConfiguration
44+
@ConditionalOnClass({ JsonParser.class, ObjectMapper.class })
45+
public class SpringAiJsonAutoConfiguration {
46+
47+
/**
48+
* Creates an ObjectMapper bean configured for lenient JSON parsing of LLM responses.
49+
* This ObjectMapper is used by {@link JsonParser} for tool calling and structured
50+
* output.
51+
*
52+
* <p>
53+
* Default configuration:
54+
* <ul>
55+
* <li>Allows unescaped control characters (common in LLM responses)</li>
56+
* <li>Ignores unknown properties</li>
57+
* <li>Serializes dates as ISO strings instead of timestamps</li>
58+
* <li>Allows empty beans to be serialized</li>
59+
* </ul>
60+
* @return configured ObjectMapper for JSON parsing
61+
*/
62+
@Bean
63+
@ConditionalOnMissingBean(name = "jsonParserObjectMapper")
64+
public ObjectMapper jsonParserObjectMapper() {
65+
ObjectMapper mapper = JsonMapper.builder()
66+
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
67+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
68+
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
69+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
70+
.addModules(JacksonUtils.instantiateAvailableModules())
71+
.build();
72+
73+
// Set this ObjectMapper to be used by JsonParser
74+
JsonParser.setObjectMapper(mapper);
75+
76+
return mapper;
77+
}
78+
79+
/**
80+
* Creates an ObjectMapper bean configured for model options
81+
* serialization/deserialization. This ObjectMapper is used by
82+
* {@link ModelOptionsUtils} for converting model options.
83+
*
84+
* <p>
85+
* Default configuration:
86+
* <ul>
87+
* <li>Ignores unknown properties</li>
88+
* <li>Allows empty beans to be serialized</li>
89+
* <li>Accepts empty strings as null objects</li>
90+
* <li>Coerces empty strings to null for Enum types</li>
91+
* </ul>
92+
* @return configured ObjectMapper for model options
93+
*/
94+
@Bean
95+
@ConditionalOnMissingBean(name = "modelOptionsObjectMapper")
96+
public ObjectMapper modelOptionsObjectMapper() {
97+
ObjectMapper mapper = JsonMapper.builder()
98+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
99+
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
100+
.addModules(JacksonUtils.instantiateAvailableModules())
101+
.build()
102+
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
103+
104+
// Configure coercion for empty strings to null for Enum types
105+
// This fixes the issue where empty string finish_reason values cause
106+
// deserialization failures
107+
mapper.coercionConfigFor(Enum.class).setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull);
108+
109+
// Set this ObjectMapper to be used by ModelOptionsUtils
110+
ModelOptionsUtils.setObjectMapper(mapper);
111+
112+
return mapper;
113+
}
114+
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.springframework.ai.autoconfigure.json.SpringAiJsonAutoConfiguration

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383

8484

8585
<module>auto-configurations/common/spring-ai-autoconfigure-retry</module>
86-
<module>auto-configurations/common/spring-ai-autoconfigure-model</module>
86+
<module>auto-configurations/common/spring-ai-autoconfigure-json</module>
8787

8888
<module>auto-configurations/models/tool/spring-ai-autoconfigure-model-tool</module>
8989

0 commit comments

Comments
 (0)