Skip to content

Commit 768140e

Browse files
committed
Add support for json.RawMessage (from github.com/shurcooL/pull/41)
1 parent 092e75a commit 768140e

File tree

3 files changed

+101
-9
lines changed

3 files changed

+101
-9
lines changed

graphql_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package graphql_test
22

33
import (
44
"context"
5+
"encoding/json"
56
"io"
67
"io/ioutil"
78
"net/http"
@@ -64,6 +65,55 @@ func TestClient_Query_partialDataWithErrorResponse(t *testing.T) {
6465
}
6566
}
6667

68+
func TestClient_Query_partialDataRawQueryWithErrorResponse(t *testing.T) {
69+
mux := http.NewServeMux()
70+
mux.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) {
71+
w.Header().Set("Content-Type", "application/json")
72+
mustWrite(w, `{
73+
"data": {
74+
"node1": { "id": "MDEyOklzc3VlQ29tbWVudDE2OTQwNzk0Ng==" },
75+
"node2": null
76+
},
77+
"errors": [
78+
{
79+
"message": "Could not resolve to a node with the global id of 'NotExist'",
80+
"type": "NOT_FOUND",
81+
"path": [
82+
"node2"
83+
],
84+
"locations": [
85+
{
86+
"line": 10,
87+
"column": 4
88+
}
89+
]
90+
}
91+
]
92+
}`)
93+
})
94+
client := graphql.NewClient("/graphql", &http.Client{Transport: localRoundTripper{handler: mux}})
95+
96+
var q struct {
97+
Node1 json.RawMessage `graphql:"node1"`
98+
Node2 *struct {
99+
ID graphql.ID
100+
} `graphql:"node2: node(id: \"NotExist\")"`
101+
}
102+
err := client.Query(context.Background(), &q, nil)
103+
if err == nil {
104+
t.Fatal("got error: nil, want: non-nil\n")
105+
}
106+
if got, want := err.Error(), "Could not resolve to a node with the global id of 'NotExist'"; got != want {
107+
t.Errorf("got error: %v, want: %v\n", got, want)
108+
}
109+
if q.Node1 == nil || string(q.Node1) != `{"id":"MDEyOklzc3VlQ29tbWVudDE2OTQwNzk0Ng=="}` {
110+
t.Errorf("got wrong q.Node1: %v\n", string(q.Node1))
111+
}
112+
if q.Node2 != nil {
113+
t.Errorf("got non-nil q.Node2: %v, want: nil\n", *q.Node2)
114+
}
115+
}
116+
67117
func TestClient_Query_noDataWithErrorResponse(t *testing.T) {
68118
mux := http.NewServeMux()
69119
mux.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) {

internal/jsonutil/graphql.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func UnmarshalGraphQL(data []byte, v interface{}) error {
4242
type decoder struct {
4343
tokenizer interface {
4444
Token() (json.Token, error)
45+
Decode(v interface{}) error
4546
}
4647

4748
// Stack of what part of input JSON we're in the middle of - objects, arrays.
@@ -68,10 +69,14 @@ func (d *decoder) Decode(v interface{}) error {
6869

6970
// decode decodes a single JSON value from d.tokenizer into d.vs.
7071
func (d *decoder) decode() error {
72+
rawMessageValue := reflect.ValueOf(json.RawMessage{})
73+
7174
// The loop invariant is that the top of each d.vs stack
7275
// is where we try to unmarshal the next JSON value we see.
7376
for len(d.vs) > 0 {
77+
var tok interface{}
7478
tok, err := d.tokenizer.Token()
79+
7580
if err == io.EOF {
7681
return errors.New("unexpected end of JSON input")
7782
} else if err != nil {
@@ -87,6 +92,8 @@ func (d *decoder) decode() error {
8792
return errors.New("unexpected non-key in JSON input")
8893
}
8994
someFieldExist := false
95+
// If one field is raw all must be treated as raw
96+
rawMessage := false
9097
for i := range d.vs {
9198
v := d.vs[i][len(d.vs[i])-1]
9299
if v.Kind() == reflect.Ptr {
@@ -97,6 +104,10 @@ func (d *decoder) decode() error {
97104
f = fieldByGraphQLName(v, key)
98105
if f.IsValid() {
99106
someFieldExist = true
107+
// Check for special embedded json
108+
if f.Type() == rawMessageValue.Type() {
109+
rawMessage = true
110+
}
100111
}
101112
}
102113
d.vs[i] = append(d.vs[i], f)
@@ -105,13 +116,20 @@ func (d *decoder) decode() error {
105116
return fmt.Errorf("struct field for %q doesn't exist in any of %v places to unmarshal", key, len(d.vs))
106117
}
107118

108-
// We've just consumed the current token, which was the key.
109-
// Read the next token, which should be the value, and let the rest of code process it.
110-
tok, err = d.tokenizer.Token()
111-
if err == io.EOF {
112-
return errors.New("unexpected end of JSON input")
113-
} else if err != nil {
114-
return err
119+
if rawMessage {
120+
// Read the next complete object from the json stream
121+
var data json.RawMessage
122+
d.tokenizer.Decode(&data)
123+
tok = data
124+
} else {
125+
// We've just consumed the current token, which was the key.
126+
// Read the next token, which should be the value, and let the rest of code process it.
127+
tok, err = d.tokenizer.Token()
128+
if err == io.EOF {
129+
return errors.New("unexpected end of JSON input")
130+
} else if err != nil {
131+
return err
132+
}
115133
}
116134

117135
// Are we inside an array and seeing next value (rather than end of array)?
@@ -136,7 +154,7 @@ func (d *decoder) decode() error {
136154
}
137155

138156
switch tok := tok.(type) {
139-
case string, json.Number, bool, nil:
157+
case string, json.Number, bool, nil, json.RawMessage:
140158
// Value.
141159

142160
for i := range d.vs {
@@ -302,7 +320,7 @@ func isGraphQLFragment(f reflect.StructField) bool {
302320
// unmarshalValue unmarshals JSON value into v.
303321
// v must be addressable and not obtained by the use of unexported
304322
// struct fields, otherwise unmarshalValue will panic.
305-
func unmarshalValue(value json.Token, v reflect.Value) error {
323+
func unmarshalValue(value interface{}, v reflect.Value) error {
306324
b, err := json.Marshal(value) // TODO: Short-circuit (if profiling says it's worth it).
307325
if err != nil {
308326
return err

internal/jsonutil/graphql_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jsonutil_test
22

33
import (
4+
"encoding/json"
45
"reflect"
56
"testing"
67
"time"
@@ -80,6 +81,29 @@ func TestUnmarshalGraphQL_jsonTag(t *testing.T) {
8081
}
8182
}
8283

84+
func TestUnmarshalGraphQL_jsonRawTag(t *testing.T) {
85+
type query struct {
86+
Data json.RawMessage
87+
Another string
88+
}
89+
var got query
90+
err := jsonutil.UnmarshalGraphQL([]byte(`{
91+
"Data": { "foo":"bar" },
92+
"Another" : "stuff"
93+
}`), &got)
94+
95+
if err != nil {
96+
t.Fatal(err)
97+
}
98+
want := query{
99+
Another: "stuff",
100+
Data: []byte(`{"foo":"bar"}`),
101+
}
102+
if !reflect.DeepEqual(got, want) {
103+
t.Errorf("not equal: %v %v", want, got)
104+
}
105+
}
106+
83107
func TestUnmarshalGraphQL_array(t *testing.T) {
84108
type query struct {
85109
Foo []graphql.String

0 commit comments

Comments
 (0)