graphql-java-codegen icon indicating copy to clipboard operation
graphql-java-codegen copied to clipboard

Java Time types cannot be used when serialising GraphQlRequest

Open chusmc opened this issue 3 years ago • 8 comments

Issue Description

We are trying to use the code-gen to generate the GraphQlRequest payload, from a graphql model with custom scalar types of type java.time

We have these custom scalars defined customTypesMapping = [ Time: "java.time.OffsetTime", ]

We found that the Json generated by graphQLRequest.toHttpJsonBody() didnt append the result of during OffsetTime.toString, which is for example "20:00Z". Debugging your task, we found these undocumented option useObjectMapperForRequestSerialization to add the type names where ObjectMapper should be used instead.

Unfortunately, the ObjetMapper your code uses, its defined as a static constant, so its not customizable. In theory if your ObjectMapper was created with new ObjectMapper().findAndRegisterModules(), it would use any jackson modules registered in the class-path, and make possible for example to have the java time types serialised

Steps to Reproduce

  1. Add custom scalar type called Time scalar Time

  2. Add a custom scalar parser so Time gets generated as java.time.OffsetTime

  3. Add this config to the gradle task

task generateGraphqlClient(type: GraphQLCodegenGradleTask) {
    graphqlSchemaPaths = ["$projectDir/src/main/resources/schema.graphqls".toString()]
    outputDir = new File("$buildDir/generated-client")
    customTypesMapping = [
            Time: "java.time.OffsetTime",
    ]
    generateClient = true
    generateBuilder = true
    generateToString = true
    useObjectMapperForRequestSerialization = ["Time"]
}

Expected Result

We should be able to serialise graphql model objects where custom scalar types are used. ObjectMapper should be able to serialise fields where a custom ObjectMapper is used, or at least where the default ObjectMapper can load any jackson modules present in the classpath

'mutation .......(......: { ..... deliveryWindow: { start: \"10:00Z\", end: \"17:00Z \"}, .....'

Actual Result

Query failed to parse :
'mutation .......(......: { ..... deliveryWindow: { start: 10:00Z, end: 17:00Z }, .....'

Your Environment and Setup

  • graphql-java-codegen version: 5.3.0
  • Build tool: Gradle
  • Mapping Config:
task generateGraphqlClient(type: GraphQLCodegenGradleTask) {
    graphqlSchemaPaths = ["$projectDir/src/main/resources/schema.graphqls".toString()]
    outputDir = new File("$buildDir/generated-client")
    customTypesMapping = [
            Time: "java.time.OffsetTime",
    ]
    generateClient = true
    generateBuilder = true
    generateToString = true
    useObjectMapperForRequestSerialization = ["Time"]
}

chusmc avatar Dec 02 '21 11:12 chusmc

Hi @chusmc Thanks for providing such detailed information. What if you manually register required modules during the application init/startup phase in the following way: GraphQLRequestSerializer.OBJECT_MAPPER.registerModule(new SimpleModule().addSerializer(new OffsetTimeSerializer())); The working test case for java.time classes can be found here: https://github.com/kobylynskyi/graphql-java-codegen/blob/f291209d204376881e886473c22bf4fdbc3af5ad/src/test/java/com/kobylynskyi/graphql/codegen/model/graphql/GraphQLRequestSerializerTest.java#L303-L317

Please let me know how it goes. Also, I've added the description of useObjectMapperForRequestSerialization to the documentation. Thanks for pointing this out!

kobylynskyi avatar Dec 03 '21 01:12 kobylynskyi

Ok. Thanks we weren't sure we should reconfigure the internal OBJECT_MAPPER

We will give it a go, as its just some unit tests,

Is it possible to suggest, that GraphQLRequestSerializer could be configured with an external injected ObjectMaper also?

Regards

chusmc avatar Dec 06 '21 12:12 chusmc

I am bumping into same problem as well and have created a sample repo here could you please check and advise? We're expecting the serialised query like below

query reviews { reviews: reviews(searchInput: { submittedDate: "2022-07-15" }){ starScore } }

but the date is unquoted

mparthasarathi avatar Jul 15 '22 08:07 mparthasarathi

use <useObjectMapperForRequestSerialization>Date</useObjectMapperForRequestSerialization>

jay3047 avatar Sep 02 '22 15:09 jay3047

Thanks @jay3047 Now I am getting below error

Java 8 date/time type java.time.LocalDate not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling

How to customize the ObjectMapper thats being created in com.kobylynskyi.graphql.codegen.utils.Utils.OBJECT_MAPPER

mparthasarathi avatar Sep 05 '22 08:09 mparthasarathi

Thanks @jay3047 Now I am getting below error

Java 8 date/time type java.time.LocalDate not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling

How to customize the ObjectMapper thats being created in com.kobylynskyi.graphql.codegen.utils.Utils.OBJECT_MAPPER

    GraphQLRequestSerializer.OBJECT_MAPPER.registerModule(
            new SimpleModule().addSerializer(new ZonedDateTimeSerializer()));


    UpdateDateMutationRequest updateDateMutationRequest = new UpdateDateMutationRequest();
    DateInput input = new DateInput();
    input.setDateTime(ZonedDateTime.parse("2020-07-30T22:17:17.884-05:00[America/Chicago]"));
    updateDateMutationRequest.setInput(input);
    GraphQLRequest graphQLRequest = new GraphQLRequest(updateDateMutationRequest);

jxnu-liguobin avatar Sep 05 '22 09:09 jxnu-liguobin

One mentioned by @jxnu-liguobin does work , but I created custom Deserializer something like below and it worked

GraphQLRequestSerializer.OBJECT_MAPPER.registerModule( SimpleModule().addSerializer( DateSerializerKotlin() ) )

`class DateSerializerKotlin : JsonSerializer<LocalDate>() { override fun serialize( localDate: LocalDate, gen: JsonGenerator?, serializers: SerializerProvider ) { val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") val formattedString: String = localDate?.format(formatter) gen!!.writeString(formattedString) }

override fun handledType(): Class<LocalDate>? {
    return LocalDate::class.java
}

}`

jay3047 avatar Sep 05 '22 09:09 jay3047

One mentioned by @jxnu-liguobin does work , but I created custom Deserializer something like below and it worked

GraphQLRequestSerializer.OBJECT_MAPPER.registerModule( SimpleModule().addSerializer( DateSerializerKotlin() ) )

class DateSerializerKotlin : JsonSerializer() { override fun serialize( localDate: LocalDate, gen: JsonGenerator?, serializers: SerializerProvider ) { val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") val formattedString: String = localDate?.format(formatter) gen!!.writeString(formattedString) }

override fun handledType(): Class<LocalDate>? {
    return LocalDate::class.java
}

}

This is good if you need this more flexible format. 👍🏻

jxnu-liguobin avatar Sep 05 '22 09:09 jxnu-liguobin