@@ -79,14 +79,16 @@ public class GraphQlArgumentBinder {
7979
8080 private final @ Nullable SimpleTypeConverter typeConverter ;
8181
82+ private final @ Nullable NameResolver nameResolver ;
83+
8284 private final boolean fallBackOnDirectFieldAccess ;
8385
8486
8587 /**
8688 * Default constructor.
8789 */
8890 public GraphQlArgumentBinder () {
89- this (( Options ) null );
91+ this (Options . create () );
9092 }
9193
9294 /**
@@ -96,7 +98,7 @@ public GraphQlArgumentBinder() {
9698 */
9799 @ Deprecated (since = "2.0" , forRemoval = true )
98100 public GraphQlArgumentBinder (@ Nullable ConversionService conversionService ) {
99- this (conversionService , false );
101+ this (Options . create (). conversionService ( conversionService ) );
100102 }
101103
102104 /**
@@ -107,13 +109,13 @@ public GraphQlArgumentBinder(@Nullable ConversionService conversionService) {
107109 */
108110 @ Deprecated (since = "2.0" , forRemoval = true )
109111 public GraphQlArgumentBinder (@ Nullable ConversionService service , boolean fallBackOnDirectFieldAccess ) {
110- this .typeConverter = initTypeConverter (service );
111- this .fallBackOnDirectFieldAccess = fallBackOnDirectFieldAccess ;
112+ this (Options .create ().conversionService (service ).fallBackOnDirectFieldAccess (fallBackOnDirectFieldAccess ));
112113 }
113114
114- public GraphQlArgumentBinder (@ Nullable Options options ) {
115- this .typeConverter = ((options != null ) ? initTypeConverter (options .conversionService ()) : null );
116- this .fallBackOnDirectFieldAccess = (options != null && options .fallBackOnDirectFieldAccess ());
115+ public GraphQlArgumentBinder (Options options ) {
116+ this .typeConverter = initTypeConverter (options .conversionService ());
117+ this .nameResolver = options .nameResolver ();
118+ this .fallBackOnDirectFieldAccess = options .fallBackOnDirectFieldAccess ();
117119 }
118120
119121 private static @ Nullable SimpleTypeConverter initTypeConverter (@ Nullable ConversionService service ) {
@@ -198,6 +200,10 @@ public GraphQlArgumentBinder(@Nullable Options options) {
198200 Assert .state (targetClass != null , "Could not resolve target type for: " + targetType );
199201 }
200202
203+ if (this .nameResolver != null ) {
204+ name = this .nameResolver .resolveName (name );
205+ }
206+
201207 Object value ;
202208 if (rawValue == null || targetClass == Object .class ) {
203209 value = rawValue ;
@@ -302,8 +308,21 @@ private Map<?, Object> bindMapToMap(
302308 ResolvableType targetType = ResolvableType .forType (
303309 ResolvableType .forConstructorParameter (constructor , i ).getType (), ownerType );
304310
311+ Object rawValue = rawMap .get (name );
312+ boolean isNotPresent = !rawMap .containsKey (name );
313+
314+ if (rawValue == null && this .nameResolver != null ) {
315+ for (String key : rawMap .keySet ()) {
316+ if (this .nameResolver .resolveName (key ).equals (name )) {
317+ rawValue = rawMap .get (key );
318+ isNotPresent = false ;
319+ break ;
320+ }
321+ }
322+ }
323+
305324 constructorArguments [i ] = bindRawValue (
306- name , rawMap . get ( name ), ! rawMap . containsKey ( name ) , targetType , paramTypes [i ], bindingResult );
325+ name , rawValue , isNotPresent , targetType , paramTypes [i ], bindingResult );
307326 }
308327
309328 Object target ;
@@ -342,6 +361,9 @@ private void bindViaSetters(Object target,
342361
343362 for (Map .Entry <String , Object > entry : rawMap .entrySet ()) {
344363 String key = entry .getKey ();
364+ if (this .nameResolver != null ) {
365+ key = this .nameResolver .resolveName (key );
366+ }
345367 TypeDescriptor typeDescriptor = beanWrapper .getPropertyTypeDescriptor (key );
346368 if (typeDescriptor == null && this .fallBackOnDirectFieldAccess ) {
347369 Field field = ReflectionUtils .findField (beanWrapper .getWrappedClass (), key );
@@ -403,10 +425,15 @@ public static final class Options {
403425
404426 private final @ Nullable ConversionService conversionService ;
405427
428+ private final @ Nullable NameResolver nameResolver ;
429+
406430 private final boolean fallBackOnDirectFieldAccess ;
407431
408- private Options (@ Nullable ConversionService conversionService , boolean fallBackOnDirectFieldAccess ) {
432+ private Options (@ Nullable ConversionService conversionService , @ Nullable NameResolver nameResolver ,
433+ boolean fallBackOnDirectFieldAccess ) {
434+
409435 this .conversionService = conversionService ;
436+ this .nameResolver = nameResolver ;
410437 this .fallBackOnDirectFieldAccess = fallBackOnDirectFieldAccess ;
411438 }
412439
@@ -416,7 +443,16 @@ private Options(@Nullable ConversionService conversionService, boolean fallBackO
416443 * @param service the service to use
417444 */
418445 public Options conversionService (@ Nullable ConversionService service ) {
419- return new Options (service , this .fallBackOnDirectFieldAccess );
446+ return new Options (service , this .nameResolver , this .fallBackOnDirectFieldAccess );
447+ }
448+
449+ /**
450+ * Add a resolver to help to map GraphQL argument names to Object property names.
451+ * @param resolver the resolver to add
452+ */
453+ public Options nameResolver (NameResolver resolver ) {
454+ resolver = ((this .nameResolver != null ) ? this .nameResolver .andThen (resolver ) : resolver );
455+ return new Options (this .conversionService , resolver , this .fallBackOnDirectFieldAccess );
420456 }
421457
422458 /**
@@ -427,13 +463,17 @@ public Options conversionService(@Nullable ConversionService service) {
427463 * @param fallBackOnDirectFieldAccess whether to fall back on direct field access
428464 */
429465 public Options fallBackOnDirectFieldAccess (boolean fallBackOnDirectFieldAccess ) {
430- return new Options (this .conversionService , fallBackOnDirectFieldAccess );
466+ return new Options (this .conversionService , this . nameResolver , fallBackOnDirectFieldAccess );
431467 }
432468
433469 public @ Nullable ConversionService conversionService () {
434470 return this .conversionService ;
435471 }
436472
473+ public @ Nullable NameResolver nameResolver () {
474+ return this .nameResolver ;
475+ }
476+
437477 public boolean fallBackOnDirectFieldAccess () {
438478 return this .fallBackOnDirectFieldAccess ;
439479 }
@@ -442,7 +482,33 @@ public boolean fallBackOnDirectFieldAccess() {
442482 * Create an instance without any options set.
443483 */
444484 public static Options create () {
445- return new Options (null , false );
485+ return new Options (null , (name ) -> name , false );
486+ }
487+ }
488+
489+
490+ /**
491+ * Contract to customize the mapping of GraphQL argument names to Object
492+ * properties. This can be useful for dealing with naming conventions like
493+ * the use of "-" that cannot be used in Java property names.
494+ * @since 2.0.0
495+ */
496+ public interface NameResolver {
497+
498+ /**
499+ * Resolve the given GraphQL argument name to an Object property name.
500+ * @param name the argument name
501+ * @return the resolved name to use
502+ */
503+ String resolveName (String name );
504+
505+ /**
506+ * Append another resolver to be invoked after the current one.
507+ * @param resolver the resolver to invoked
508+ * @return a new composite resolver
509+ */
510+ default NameResolver andThen (NameResolver resolver ) {
511+ return (name ) -> resolver .resolveName (resolveName (name ));
446512 }
447513 }
448514
0 commit comments