Amplify-android: GsonVariablesSerializer should support ISO 8601 format for Date type

Created on 2 Jan 2020  路  9Comments  路  Source: aws-amplify/amplify-android

My GraphQL schema uses createdAt: AWSDateTime! type, and the auto-generated POJO class has a java.util.Date type for createdAt field.
When Amplify Android try to make a GraphQL query, this POJO is converted to a query string with using com.amplifyframework.api.aws.GsonVariablesSerializer by default which converts java.util.Date to yyyy-MM-dd format transparently.

public final class GsonVariablesSerializer implements GraphQLRequest.VariablesSerializer {
    @Override
    public String serialize(Map<String, Object> variables) {
        return new GsonBuilder()
                .registerTypeAdapter(Date.class, new DateSerializer())
                .create()
                .toJson(variables);
    }

    class DateSerializer implements JsonSerializer<Date> {
        public JsonElement serialize(Date date, Type typeOfSrc, JsonSerializationContext context) {
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
            return new JsonPrimitive(df.format(new Date()));
        }
    }
}

I believe AWSDateTime is ISO 8601 compliant according to the document, so this should be "yyyy-MM-dd'T'HH:mm:ssXXX" in order to keep the degree of precision.
https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html

To work around this, I know I can make GraphQLRequest manually as follows:

// make your own Serializer
class GsonVariablesSerializer : VariablesSerializer {
    override fun serialize(variables: Map<String, Any>): String {
        return GsonBuilder()
            .registerTypeAdapter(
                Date::class.java,
                DateSerializer()
            )
            .create()
            .toJson(variables)
    }

    internal inner class DateSerializer : JsonSerializer<Date?> {
        override fun serialize(
            date: Date?,
            typeOfSrc: Type,
            context: JsonSerializationContext
        ): JsonElement {
            val df: DateFormat = SimpleDateFormat(
                "yyyy-MM-dd'T'HH:mm:ssXXX",
                Locale.getDefault()
            )
            return JsonPrimitive(df.format(Date()))
        }
    }
}

// make manual request
val query = """
    mutation CreateMessage(
      ${'$'}id: ID!
      ${'$'}content: String!
      ${'$'}roomId: String!
      ${'$'}username: String!
      ${'$'}createdAt: AWSDateTime!
    ) {
      createMessage(input: {
        id: ${'$'}id
        content: ${'$'}content
        roomId: ${'$'}roomId
        username: ${'$'}username
        createdAt: ${'$'}createdAt
      }) {
        id
        content
        roomId
        username
        createdAt
      }
    }
    """.trimIndent()
val variables: Map<String, Any> = mutableMapOf(
    "id" to id,
    "content" to content,
    "roomId" to roomId,
    "username" to username,
    "createdAt" to createdAt
)
val request =
    GraphQLRequest(query, variables, Message::class.java, GsonVariablesSerializer())

Amplify.API.mutate(
    request,
    object :
        ResultListener<GraphQLResponse<Message>> {
        override fun onResult(response: GraphQLResponse<Message>) {
        }

        override fun onError(e: Throwable) {
        }
    }
)

However, this is too laborious.
My suggestions are:

  1. Make GsonVariablesSerializer ISO 8601 compliant.
    This is desirable because AWSDateTime is ISO 8601 compliant itself.

  2. Add an API to register type-to-type mapping
    Add an API that users can easily pass the type-to-type mapping to the serializer/deserializer.
    Or add override methods to Amplify.API.query/mutate to accept the custom Serializer.

Thanks!

API Bug

All 9 comments

Thanks for bringing this up - it's definitely a bug that AWSDateTime is stripping the time so we will address that here.

Am I misunderstanding? It looks like in the original code and the proposed fix, that the input date is not even being used! This is what I'm running into when trying to store a date. It doesn't matter what I date I set it only sends the current day.

Could you link to the code and fix you're referring to @emshach?

Hi @TrekSoft,
in https://github.com/aws-amplify/amplify-android/issues/216#issue-544769682

    class DateSerializer implements JsonSerializer<Date> {
        public JsonElement serialize(Date date, Type typeOfSrc, JsonSerializationContext context) {
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
            return new JsonPrimitive(df.format(new Date()));

Here, I think df.format(new Date()) should be df.format(date)

and the recommended solution has the same

            val df: DateFormat = SimpleDateFormat(
                "yyyy-MM-dd'T'HH:mm:ssXXX",
                Locale.getDefault()
            )
            return JsonPrimitive(df.format(Date()))

Oof, good catch. Will prioritize the fix for this.

Fixed that bug @emshach and merged it into master so it'll go out with the next release.

We'll work on the main topic of this issue separately as part of our push to get API ready for GA.

Cool! Thanks.

AWSDate, AWSTime, AWSDateTime, and AWSTimestamp are now fully supported in the Temporal class. JSON deserialization is provided in the TemporalalDeserializers, which is used by the aws-api plugin while deserializing GraphQL responses.

Cool! Will try it out later!

Was this page helpful?
0 / 5 - 0 ratings