1616
1717package org .springframework .ai .model ;
1818
19+ import com .fasterxml .jackson .annotation .JsonAnyGetter ;
1920import java .beans .PropertyDescriptor ;
2021import java .lang .reflect .Field ;
22+ import java .lang .reflect .Method ;
2123import java .lang .reflect .Type ;
2224import java .util .ArrayList ;
2325import java .util .Arrays ;
@@ -83,7 +85,7 @@ public abstract class ModelOptionsUtils {
8385
8486 private static final List <String > BEAN_MERGE_FIELD_EXCISIONS = List .of ("class" );
8587
86- private static final ConcurrentHashMap <Class <?>, List < String > > REQUEST_FIELD_NAMES_PER_CLASS = new ConcurrentHashMap <>();
88+ private static final ConcurrentHashMap <Class <?>, JsonPropertyResult > REQUEST_FIELD_NAMES_PER_CLASS = new ConcurrentHashMap <>();
8789
8890 private static final AtomicReference <SchemaGenerator > SCHEMA_GENERATOR_CACHE = new AtomicReference <>();
8991
@@ -182,10 +184,14 @@ public static <T> T merge(Object source, Object target, Class<T> clazz, List<Str
182184 }
183185
184186 List <String > requestFieldNames = CollectionUtils .isEmpty (acceptedFieldNames )
185- ? REQUEST_FIELD_NAMES_PER_CLASS .computeIfAbsent (clazz , ModelOptionsUtils ::getJsonPropertyValues )
187+ ? REQUEST_FIELD_NAMES_PER_CLASS .computeIfAbsent (clazz , ModelOptionsUtils ::getJsonPropertyResult )
188+ .properties ()
186189 : acceptedFieldNames ;
187190
188- if (CollectionUtils .isEmpty (requestFieldNames )) {
191+ boolean acceptAllFields = REQUEST_FIELD_NAMES_PER_CLASS .containsKey (clazz )
192+ && REQUEST_FIELD_NAMES_PER_CLASS .get (clazz ).acceptAllFields ;
193+
194+ if (!acceptAllFields && CollectionUtils .isEmpty (requestFieldNames )) {
189195 throw new IllegalArgumentException ("No @JsonProperty fields found in the " + clazz .getName ());
190196 }
191197
@@ -199,7 +205,7 @@ public static <T> T merge(Object source, Object target, Class<T> clazz, List<Str
199205
200206 targetMap = targetMap .entrySet ()
201207 .stream ()
202- .filter (e -> requestFieldNames .contains (e .getKey ()))
208+ .filter (e -> acceptAllFields || requestFieldNames .contains (e .getKey ()))
203209 .collect (Collectors .toMap (Map .Entry ::getKey , Map .Entry ::getValue ));
204210
205211 return ModelOptionsUtils .mapToClass (targetMap , clazz );
@@ -263,20 +269,33 @@ public static <T> T mapToClass(Map<String, Object> source, Class<T> clazz) {
263269 }
264270
265271 /**
266- * Returns the list of name values of the {@link JsonProperty} annotations .
272+ * Returns the result contains tag of accept all fields or list of json fields .
267273 * @param clazz the class that contains fields annotated with {@link JsonProperty}.
268- * @return the list of values of the {@link JsonProperty} annotations.
274+ * @return the result contains tag of method has {@link JsonAnyGetter} annotation or
275+ * list of values of the {@link JsonProperty} annotations.
269276 */
270- public static List < String > getJsonPropertyValues (Class <?> clazz ) {
277+ public static JsonPropertyResult getJsonPropertyResult (Class <?> clazz ) {
271278 List <String > values = new ArrayList <>();
272279 Field [] fields = clazz .getDeclaredFields ();
280+ Method [] methods = clazz .getDeclaredMethods ();
281+ boolean hasAnyGetter = false ;
282+
283+ for (Method method : methods ) {
284+ // Iterate through the method to check JsonAnyGetter annotation to ensure that unknown parameters can be copied
285+ JsonAnyGetter anyGetterAnnotation = method .getAnnotation (JsonAnyGetter .class );
286+ if (anyGetterAnnotation != null ) {
287+ hasAnyGetter = true ;
288+ return new JsonPropertyResult (hasAnyGetter , values );
289+ }
290+ }
291+
273292 for (Field field : fields ) {
274293 JsonProperty jsonPropertyAnnotation = field .getAnnotation (JsonProperty .class );
275294 if (jsonPropertyAnnotation != null ) {
276295 values .add (jsonPropertyAnnotation .value ());
277296 }
278297 }
279- return values ;
298+ return new JsonPropertyResult ( false , values ) ;
280299 }
281300
282301 /**
@@ -452,4 +471,10 @@ public static <T> T mergeOption(T runtimeValue, T defaultValue) {
452471 return ObjectUtils .isEmpty (runtimeValue ) ? defaultValue : runtimeValue ;
453472 }
454473
474+ /**
475+ * Record the decision result of {@link #getJsonPropertyResult(Class)}
476+ * @param acceptAllFields the current class allows tags for all properties
477+ * @param properties list of properties allowed by the current class
478+ */
479+ public record JsonPropertyResult (boolean acceptAllFields , List <String > properties ) {}
455480}
0 commit comments