shurcooL/graphql

support JSON scalar type

andreimc opened this issue · 11 comments

struct field for status doesn't exist in any of 2 places to unmarshal

I have a JSON type and it seems to be expecting the whole object before running my unmarshal.

is there any way to ignore this given it's a JSON scalar return?

Thanks for the report. How can I reproduce this?

@dmitshur I have same issue too, My GQL like struct below
query { example(id:"123456") { id jsondata } }
jsondata result example is {foo: bar}

There's not enough information there for me to reproduce this. Can you please post a minimal working Go program that triggers that error? Or at least include the query (or a minified version of it) you wrote, and the JSON response from the GraphQL server?

Here's a starting point to make it easier; you just need to replace the query and JSON response:

package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"

	"github.com/shurcooL/graphql"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		io.WriteString(w, `{"data": {"viewer": {"login": "gopher", "bio": "The Go gopher."}}}`)
	})
	client := graphql.NewClient("/graphql", &http.Client{Transport: localRoundTripper{handler: mux}})

	var query struct {
		Viewer struct {
			Login     string
			Biography string `graphql:"bio"`
		}
	}
	err := client.Query(context.Background(), &query, nil)
	fmt.Println("error:", err)
	fmt.Printf("query: %+v\n", query)

	// Output:
	// error: <nil>
	// query: {Viewer:{Login:gopher Biography:The Go gopher.}}
}

// localRoundTripper is an http.RoundTripper that executes HTTP transactions
// by using handler directly, instead of going over an HTTP connection.
type localRoundTripper struct {
	handler http.Handler
}

func (l localRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	w := httptest.NewRecorder()
	l.handler.ServeHTTP(w, req)
	return w.Result(), nil
}

I'm also running into this issue. I am using Prisma as my GraphQL Server, which provides a scalar Json type. Consider the following schema:

type Query {
  metaObjectType(id:ID!): MetaObjectType
}
type MetaObjectType {
  parentId: String!
  meta: Json!
}

The following query is valid.

query {
  metaObjectType(id:$id) {
    parentId
    meta
  }
}

The following query is invalid, and produces an error: "Field 'meta' of type 'Json' must not have a sub selection."

query {
  metaObjectType(id:$id) {
    parentId
    meta {
      someMetaKey
    }
  }
}

I'd like to define my struct like this:

type MyQuery struct {
  MetaObjectType struct {
    ParentID string
    Meta     json.RawMessage
  } `graphql:"metaObjectType(id:$id)"`
}

However, as reported above, the "no place to unmarshal" error is returned when the response json contains more fields than the provided struct.

My vote is to queries to be specified as json.RawMessage for this use case.

I have gone ahead and introduced the issue into the boilerplate code you provided above.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"

	"github.com/shurcooL/graphql"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		io.WriteString(w, `{"data": {"viewer": {"login": "gopher", "bio": "The Go gopher.", "meta": {"foo": "bar"}}}}`)
	})
	client := graphql.NewClient("/graphql", &http.Client{Transport: localRoundTripper{handler: mux}})

	var query struct {
		Viewer struct {
			Login     string
			Biography string `graphql:"bio"`
			Meta      json.RawMessage
		}
	}
	err := client.Query(context.Background(), &query, nil)
	fmt.Println("error:", err)
	fmt.Printf("query: %+v\n", query)

	// Output:
	// error: <nil>
	// query: {Viewer:{Login:gopher Biography:The Go gopher.}}
}

// localRoundTripper is an http.RoundTripper that executes HTTP transactions
// by using handler directly, instead of going over an HTTP connection.
type localRoundTripper struct {
	handler http.Handler
}

func (l localRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	w := httptest.NewRecorder()
	l.handler.ServeHTTP(w, req)
	return w.Result(), nil
}

With the changes from #41:

package main
  
import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "net/http"
        "net/http/httptest"

        "github.com/shurcooL/graphql"
)

func main() {
        mux := http.NewServeMux()
        mux.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) {
                w.Header().Set("Content-Type", "application/json")
                io.WriteString(w, `{"data": {"viewer": {"login": "gopher", "bio": "The Go gopher.", "meta": {"foo": "bar"}}}}`)
        })
        client := graphql.NewClient("/graphql", &http.Client{Transport: localRoundTripper{handler: mux}})

        var query struct {
                Viewer struct {
                        Login     string
                        Biography string `graphql:"bio"`
                        Meta      json.RawMessage
                }
        }
        err := client.Query(context.Background(), &query, nil)
        fmt.Println("error:", err)
        fmt.Printf("query: %+v\n", query)
        fmt.Printf("raw: %+v\n", string(query.Viewer.Meta))

        // Output:
        // error: <nil>
        // query: {Viewer:{Login:gopher Biography:The Go gopher. Meta:[123 34 102 111 111 34 58 34 98 97 114 34 125]}}
        // raw: {"foo":"bar"}
}

// localRoundTripper is an http.RoundTripper that executes HTTP transactions
// by using handler directly, instead of going over an HTTP connection.
type localRoundTripper struct {
        handler http.Handler
}

func (l localRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
        w := httptest.NewRecorder()
        l.handler.ServeHTTP(w, req)
        return w.Result(), nil
}
cbrunner-mac:graphql cbrunner$ go run cmd/main.go 
error: <nil>
query: {Viewer:{Login:gopher Biography:The Go gopher. Meta:[123 34 102 111 111 34 58 34 98 97 114 34 125]}}
raw: {"foo":"bar"}

Thank you @adamgoose for providing additional information and the reproducible snippet. It has helped a lot, I'm able to understand this issue now. I haven't run into it myself because GitHub API v4 doesn't have a JSON scalar type.

Thanks for working on a PR @cameronbrunner, and sorry about the slow response there. I don't have a lot of bandwidth left to do reviews in this repo unfortunately.

Using json.RawMessage seems reasonable, but I'll want to think if there any other solutions, and then I'll try to find some time for code review. I just wanted to post this update here for now.

Bumped into this issue. Any update on supporting this?
Thanks

cor commented

Is there a workaround for this? I really need to insert a jsonb into a Hasura database

@cor My #41 branch is still here... https://github.com/Navops/graphql/tree/support-raw-json (the v0.0.2 tag is pointing at this branches current HEAD/tip). You can use that directly, fork it, etc. We do something similar in our project by adding the following line to our go.mod

replace github.com/shurcooL/graphql => github.com/navops/graphql v0.0.2

This is now supported in my transport-agnostic fork: https://github.com/deref/graphql-go

The specific changes are here: deref/graphql-go@c7885fd...e2b4739