3
votes

I am trying to write deserialization code for responses of user-defined GraphQL queries. The code has access to the query response in JSON-serialized form and the underlying GraphQL schema (by querying the endpoint's schema.json or making introspection requests).

Assume the following schema:

scalar Date

type User {
    name: String
    birthday: Date
}

type Query {
    allUsers: [User]
}

schema {
    query: Query
}

And the following query:

query {
    allUsers {
        name
        birthday
    }
}

The response may look like this (only includes the data.allUsers-field from the full response for brevity):

[
    {"name": "John Doe", "birthday": "1983-12-07"}
]

What I am attempting to do is deserialize the above response in a manner that preserves type information, including for any custom scalars. In the above example, I know by convention that the GraphQL scalar Date should be deserialized as LocalDate in Java, but just from the response alone I do not know that the birthday field represents the GraphQL scalar type Date, since it's serialized as a regular string in JSON.

What I can do is try to utilize the GraphQL schema for this. For the above example, the schema may look something like this (shortened for brevity):

...
"types": [
{
  "kind": "OBJECT",
  "name": "User",
  "fields": [
    {
      "name": "name",
      "type": {
        "kind": "SCALAR",
        "name": "String"
      }
    },
    {
      "name": "birthday"
      "type": {
        "kind": "SCALAR",
        "name": "Date"
      }
    }
...

From this information I can deduce that that response's birthday field is of type Date, and deserialize it accordingly. However, things get more complicated if the query uses non-trivial GraphQL features. Take aliasing for example:

query {
    allUsers {
        name
        dayOfBirth: birthday
    }
}

At this point I would already need to keep track of any aliasing (which I could do since that information is available if I parse the query), and backtrack those to find the correct type. I fear it might get even more complicated if e.g. fragments are used.

Given that I use graphql-java, and it appears to already need to handle all of these cases for serialization, I wondered if there was an easier way to do this than to manually backtrack the types from the query and schema.

2

2 Answers

1
votes

How about generating java classes from the schema and then using those classes to deserialize. There is one plugin which I have used before for this - graphql-java-generator

You may need to enhance the plugin a bit to support your custom scalars though

It basically generates a java client for invoking your GraphQL queries in a Java way.

0
votes

I had the same problem to deserialize an LocalDate attribute, even using the graphql-java-extended-scalars library.

Researching I found that this library works well for queries but not so well for mutations.

I fixed my problem by customizing SchemaParserOptions, like this:

@Bean
public SchemaParserOptions schemaParserOptions() {
    return SchemaParserOptions.newOptions().objectMapperConfigurer((mapper, context) -> {
        mapper.registerModule(new JavaTimeModule());
    }).build();
}

In the object i didn't use any serialization and deserialization annotations.