React and GraphQL, plus GoLang

GraphQL DateTime Scalar Value

Strings are not your friend when it comes to date time types. The problem is that with the naive definition you'll get the wrong value. For starters the naive definition looks like this:

 1// GraphQL
 2//     type Object {
 3//         updated_at  String
 4//     }
 5//
 6
 7// The Go structure definition
 8struct {
 9	// ....
10	UpdatedAt time.Time `json:"updated_at" sql:"NOT NULL"`
11}
12
13// GoLang GraphQL definition
14oType := graphql.NewObject(
15    graphql.ObjectConfig{
16        Name: "Object",
17        Fields: graphql.FieldsThunk(func () graphql.Fields {
18            return graphql.Fields{
19			    //  ...
20                "updated_at": &graphql.Field{
21                    Type: graphql.String,
22                },
23				//  ...

You've always serialized out time.Time as String with JSON and it's worked great. The Safari throws an Exception on parsing a date and you're perplexed, after all it worked in Chrome. You beging to debug and discover that:

  • JSON Serialize a time.Time = 2016-11-10T11:34:33.91982067-05:00
  • Go GraphQL Serialize a time.Time = 2016-11-10 11:34:33.91982067 -0500 EST

Clearly a big difference, but why? As it turns out GraphQL does a "%v" on all objects that aren't strings. While the JSON encoder knows that it should make a ISO time print. Not only that but Chrome is more forgiving about the parsing of Date() than other browsers.

Give me the code

You should define a DateTime data type to support the output of ISO3339 (a stricter version of ISO8601). I will admit that I got the idea from reading the GitHub GraphQL definitions.

Here's the code to define a DateTime for GoLang's GraphQL library. I do wonder a bit why DateTime isn't part of the GraphQL definition, since having a standard would be exceptionally useful.

 1package handlers
 2
 3import (
 4    "time"
 5    "fmt"
 6    "github.com/graphql-go/graphql"
 7    "github.com/graphql-go/graphql/language/ast"
 8)
 9
10var dateTimeType = graphql.NewScalar(graphql.ScalarConfig{
11        Name: "DateTime",
12        Description: "DateTime is a DateTime in ISO 8601 format",
13        Serialize: func(value interface{}) interface{} {
14            switch value := value.(type) {
15            case time.Time:
16                return value.Format(time.RFC3339)
17            }
18            fmt.Println("Got invalid type %T", value)
19            return "INVALID"
20        },
21        ParseValue: func(value interface{}) interface{} {
22            switch tvalue := value.(type) {
23            case string:
24                if tval, err := time.Parse(time.RFC3339, tvalue); err != nil {
25                    return nil
26                } else {
27                    return tval
28                }
29            }
30            return nil
31        },
32        ParseLiteral: func(valueAST ast.Value) interface{} {
33            switch valueAST := valueAST.(type) {
34            case *ast.StringValue:
35                return valueAST.Value
36            }
37            return nil
38        },
39    },
40)

Now you have a very useful DateTime type to use in your GraphQL definitions so you can have a type that looks like:

1type Object {
2	updated_at	DateTime
3}