I'm using graphlql-go in conjunction with mgo and would like to know how to setup the _id field.
// Customer describes a customer
type Customer struct {
ID bson.ObjectId `json:"_id,omitempty" bson:"_id,omitempty"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
LastSeenAt time.Time `json:"lastSeenAt" bson:"lastSeenAt"`
}
// CustomerType is the graphql representation of a Customer
var CustomerType = graphql.NewObject(graphql.ObjectConfig{
Name: "Customer",
Fields: graphql.Fields{
"_id": &graphql.Field{
Type: graphql.String,
},
"createdAt": &graphql.Field{
Type: graphql.DateTime,
},
"lastSeenAt": &graphql.Field{
Type: graphql.DateTime,
},
},
})
If I now query all customers with:
{
customers {
_id
createdAt
lastSeenAt
}
}
I receive this answer:
{
"data": {
"customers": [
{
"_id": "ObjectIdHex(\"5b3f4d00d73eb0194e672a8a\")",
"createdAt": "0001-01-01T00:00:00Z",
"lastSeenAt": "0001-01-01T00:00:00Z"
}
]
}
}
How can I tell graphql-go to use the .Hex() method to stringify the ObjectId when marshaling JSON?
Hi @michelalbers, thanks a lot for using the lib.
That's correct, in your use case, you have to let know graphql-go about the bson.ObjectId type, and the way we can do that is via a custom scalar.
I went ahead and wrote a working example with a custom BSON scalar:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"github.com/graphql-go/graphql"
"github.com/graphql-go/graphql/language/ast"
)
type Customer struct {
ID bson.ObjectId `json:"_id" bson:"_id"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
LastSeenAt time.Time `json:"lastSeenAt" bson:"lastSeenAt"`
}
var BSON = graphql.NewScalar(graphql.ScalarConfig{
Name: "BSON",
Description: "The `bson` scalar type represents a BSON Object.",
// Serialize serializes `bson.ObjectId` to string.
Serialize: func(value interface{}) interface{} {
switch value := value.(type) {
case bson.ObjectId:
return value.Hex()
case *bson.ObjectId:
v := *value
return v.Hex()
default:
return nil
}
},
// ParseValue parses GraphQL variables from `string` to `bson.ObjectId`.
ParseValue: func(value interface{}) interface{} {
switch value := value.(type) {
case string:
return bson.ObjectIdHex(value)
case *string:
return bson.ObjectIdHex(*value)
default:
return nil
}
return nil
},
// ParseLiteral parses GraphQL AST to `bson.ObjectId`.
ParseLiteral: func(valueAST ast.Value) interface{} {
switch valueAST := valueAST.(type) {
case *ast.StringValue:
return bson.ObjectIdHex(valueAST.Value)
}
return nil
},
})
var CustomerType = graphql.NewObject(graphql.ObjectConfig{
Name: "Customer",
Fields: graphql.Fields{
"_id": &graphql.Field{
Type: BSON,
},
"createdAt": &graphql.Field{
Type: graphql.DateTime,
},
"lastSeenAt": &graphql.Field{
Type: graphql.DateTime,
},
},
})
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
log.Fatal(err)
}
db := session.DB("testdb")
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"customers": &graphql.Field{
Type: graphql.NewList(CustomerType),
Args: graphql.FieldConfigArgument{
"_id": &graphql.ArgumentConfig{
Type: BSON,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// _id := p.Args["_id"]
// log.Printf("_id args: %+v", _id)
var customers []Customer
if err := db.C("customers").Find(nil).All(&customers); err != nil {
return nil, err
}
return customers, nil
},
},
},
}),
Types: []graphql.Type{BSON},
})
if err != nil {
log.Fatal(err)
}
query := "{ customers { _id, createdAt, lastSeenAt } }"
/*
queryWithVariable := `
query($id: BSON) {
customers(_id: $id) {
_id
createdAt
lastSeenAt
}
}
`
queryWithArgument := `
query {
customers(_id: "5b42ba57289fb278f2a4a13f") {
_id
createdAt
lastSeenAt
}
}
`
*/
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
VariableValues: map[string]interface{}{
"id": "5b42ba57289fb278f2a4a13f",
},
})
if len(result.Errors) > 0 {
log.Fatal(result)
}
b, err := json.Marshal(result)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
}
(mgo)-> go run main.go | jq
{
"data": {
"customers": [
{
"_id": "5b42ba57289fb278f2a4a13f",
"createdAt": "2018-07-09T01:28:55.711Z",
"lastSeenAt": "2018-07-09T01:28:55.711Z"
},
{
"_id": "5b42ba5a289fb278f2a4a140",
"createdAt": "2018-07-09T01:28:58.929Z",
"lastSeenAt": "2018-07-09T01:28:58.929Z"
}
]
}
}
Thank you so much @chris-ramon for pointing out and implementing it! Maybe we should include this in the library itself since I think this is a very common case.
Somehow the type switch in Serialize does not work as expected. It falls through in the default case and when I print the type via:
println(reflect.TypeOf(value))
I receive a memory address (0x14a46a0,0xc4202321e0). I'm a Go beginner so I probably miss something. Do you have an idea what it might be?
Edit: Querying by ID via your example works as expected :)
Edit 2: My models are in a separate package. If I put them in the same package as the schema and scalar definition it works. I can probably refactor that - the question of "why?!" remains. Can someone shed some light?
Edit 3: FOUND IT! :) I was importing mgo/v2/bson in one file and globalsign/mgo/bson in the other. Now its working perfectly fine. Thank you so much!
Glad to know you have the BSON scalar setup & working @michelalbers.
Also added a custom scalar type example for further reference under, PR: https://github.com/graphql-go/graphql/pull/344
Most helpful comment
Hi @michelalbers, thanks a lot for using the lib.
That's correct, in your use case, you have to let know graphql-go about the
bson.ObjectIdtype, and the way we can do that is via a custom scalar.I went ahead and wrote a working example with a custom BSON scalar: