shurcooL / graphql

Package graphql provides a GraphQL client implementation.
MIT License
701 stars 279 forks source link

support JSON scalar type #32

Open andreimc opened 5 years ago

andreimc commented 5 years ago

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?

dmitshur commented 5 years ago

Thanks for the report. How can I reproduce this?

seta-tonypham commented 5 years ago

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

dmitshur commented 5 years ago

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
}
adamgoose commented 5 years ago

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.

adamgoose commented 5 years ago

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
}
cameronbrunner commented 5 years ago

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"}
dmitshur commented 5 years ago

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.

karthikvt26 commented 4 years ago

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

cor commented 2 years ago

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

cameronbrunner commented 2 years ago

@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

brandonbloom commented 2 years ago

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

The specific changes are here: https://github.com/deref/graphql-go/compare/c7885fdd0fbc0a4061a3d40e31a14a48b57d6726...e2b473959845be6782ba490d7372a82f3704bd4b