Repository: graphql-java-kickstart/graphql-java-tools (kotlin)

Issue:
How to map a field from a Java class to a type property with a different name?
I have a type definition with a filed that has underscores in its name:
```
type Book {
    reviews_count: Int
}
```
**Q:** Is there a possibility to use `reviewsCount` instead of `reviews_count` in Java code? (an analog of `@JsonProperty` from Jackson)

Note: I do not want to remap all camel case notations to type properties with underscores. It is only needed for this particular field.

Pull request description:
Added a search in FieldResolverScanner for getters where the field name needs to be converted from snake to camel case.

Resolves #383 

## Checklist
<!-- Change [ ] to [x] to indicate you acknowledge the check. -->
- [x] Pull requests follows the [contribution guide](https://github.com/graphql-java-kickstart/graphql-java-tools/wiki/Contribution-guide)
- [x] New or modified functionality is covered by tests

## Description
The findResolverMethod function has a cascading if/else check to try to find a matching method in a variety of formats. I added a 5th check (similar to the 3rd check) but instead of only capitalizing the fieldname I converted the fieldname from snake case to camelCase. The existing code uses Apache Commons-lang3 already, so I stayed with it to do the snake to camel case conversion.  Ideally I would have preferred to use CaseFormat from Guava or CaseUtils from Apache Commons Text, but I did not want to introduce a new dependency.

Patch:
diff --git a/src/main/kotlin/graphql/kickstart/tools/resolver/FieldResolverScanner.kt b/src/main/kotlin/graphql/kickstart/tools/resolver/FieldResolverScanner.kt
index fc8dcddd..ea8ec966 100644
--- a/src/main/kotlin/graphql/kickstart/tools/resolver/FieldResolverScanner.kt
+++ b/src/main/kotlin/graphql/kickstart/tools/resolver/FieldResolverScanner.kt
@@ -4,10 +4,7 @@ import graphql.Scalars
 import graphql.kickstart.tools.ResolverInfo
 import graphql.kickstart.tools.RootResolverInfo
 import graphql.kickstart.tools.SchemaParserOptions
-import graphql.kickstart.tools.util.GraphQLLangType
-import graphql.kickstart.tools.util.JavaType
-import graphql.kickstart.tools.util.declaredNonProxyMethods
-import graphql.kickstart.tools.util.unwrap
+import graphql.kickstart.tools.util.*
 import graphql.language.FieldDefinition
 import graphql.language.TypeName
 import graphql.schema.DataFetchingEnvironment
@@ -83,6 +80,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
         //   2. Method that returns a boolean with "is" style getter
         //   3. Method with "get" style getter
         //   4. Method with "getField" style getter
+        //   5. Method with "get" style getter with the field name converted from snake_case to camelCased. ex: key_ops -> getKeyOps()
         return methods.find {
             it.name == name && verifyMethodArguments(it, argumentCount, search)
         } ?: methods.find {
@@ -91,6 +89,8 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
             it.name == "get${name.capitalize()}" && verifyMethodArguments(it, argumentCount, search)
         } ?: methods.find {
             it.name == "getField${name.capitalize()}" && verifyMethodArguments(it, argumentCount, search)
+        } ?: methods.find {
+            it.name == "get${name.snakeToCamelCase()}" && verifyMethodArguments(it, argumentCount, search)
         }
     }
 
diff --git a/src/main/kotlin/graphql/kickstart/tools/util/Utils.kt b/src/main/kotlin/graphql/kickstart/tools/util/Utils.kt
index 9caeaf51..7ff5b506 100644
--- a/src/main/kotlin/graphql/kickstart/tools/util/Utils.kt
+++ b/src/main/kotlin/graphql/kickstart/tools/util/Utils.kt
@@ -72,3 +72,5 @@ private fun isBooleanGetter(method: Method) = (method.name.startsWith("is")
     && (method.returnType == java.lang.Boolean::class.java)
     || method.returnType == Boolean::class.java)
 
+internal fun String.snakeToCamelCase(): String = split("_").joinToString(separator = "") { it.capitalize() }
+


Test patch:
diff --git a/src/test/groovy/graphql/kickstart/tools/FieldResolverScannerSpec.groovy b/src/test/groovy/graphql/kickstart/tools/FieldResolverScannerSpec.groovy
index da87551d..e2a14b89 100644
--- a/src/test/groovy/graphql/kickstart/tools/FieldResolverScannerSpec.groovy
+++ b/src/test/groovy/graphql/kickstart/tools/FieldResolverScannerSpec.groovy
@@ -87,6 +87,18 @@ class FieldResolverScannerSpec extends Specification {
         ((MethodFieldResolver) meta).getMethod().getReturnType() == BoatInformation.class
     }
 
+    def "scanner finds field resolver method using camelCase for snake_cased field_name"() {
+        setup:
+        def resolver = new RootResolverInfo([new CamelCaseQuery1()], options)
+
+        when:
+        def meta = scanner.findFieldResolver(new FieldDefinition("hull_type", new TypeName("HullType")), resolver)
+
+        then:
+        meta instanceof MethodFieldResolver
+        ((MethodFieldResolver) meta).getMethod().getReturnType() == HullType.class
+    }
+
     class RootQuery1 implements GraphQLQueryResolver {
         def field1() {}
     }
@@ -99,6 +111,12 @@ class FieldResolverScannerSpec extends Specification {
         def field1() {}
     }
 
+    class CamelCaseQuery1 implements GraphQLQueryResolver {
+        HullType getHullType(){}
+    }
+
+    class HullType {}
+
     class ParentPropertyQuery {
         private Integer version = 1
     }