...
1
2
3 package jsonutil
4
5 import (
6 "bytes"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "io"
11 "reflect"
12 "strings"
13 )
14
15
16
17
18
19
20 func UnmarshalGraphQL(data []byte, v any) error {
21 dec := json.NewDecoder(bytes.NewReader(data))
22 dec.UseNumber()
23 err := (&decoder{tokenizer: dec}).Decode(v)
24 if err != nil {
25 return err
26 }
27 tok, err := dec.Token()
28 switch err {
29 case io.EOF:
30
31
32 return nil
33 case nil:
34 return fmt.Errorf("invalid token '%v' after top-level value", tok)
35 default:
36 return err
37 }
38 }
39
40
41
42 type decoder struct {
43 tokenizer interface {
44 Token() (json.Token, error)
45 }
46
47
48 parseState []json.Delim
49
50
51
52
53
54
55
56 vs [][]reflect.Value
57 }
58
59
60 func (d *decoder) Decode(v any) error {
61 rv := reflect.ValueOf(v)
62 if rv.Kind() != reflect.Ptr {
63 return fmt.Errorf("cannot decode into non-pointer %T", v)
64 }
65 d.vs = [][]reflect.Value{{rv.Elem()}}
66 return d.decode()
67 }
68
69
70 func (d *decoder) decode() error {
71
72
73 for len(d.vs) > 0 {
74 tok, err := d.tokenizer.Token()
75 if err == io.EOF {
76 return errors.New("unexpected end of JSON input")
77 } else if err != nil {
78 return err
79 }
80
81 switch {
82
83
84 case d.state() == '{' && tok != json.Delim('}'):
85 key, ok := tok.(string)
86 if !ok {
87 return errors.New("unexpected non-key in JSON input")
88 }
89 someFieldExist := false
90 for i := range d.vs {
91 v := d.vs[i][len(d.vs[i])-1]
92 if v.Kind() == reflect.Ptr {
93 v = v.Elem()
94 }
95 var f reflect.Value
96 if v.Kind() == reflect.Struct {
97 f = fieldByGraphQLName(v, key)
98 if f.IsValid() {
99 someFieldExist = true
100 }
101 }
102 d.vs[i] = append(d.vs[i], f)
103 }
104 if !someFieldExist {
105 return fmt.Errorf("struct field for %q doesn't exist in any of %v places to unmarshal", key, len(d.vs))
106 }
107
108
109
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
115 }
116
117
118 case d.state() == '[' && tok != json.Delim(']'):
119 someSliceExist := false
120 for i := range d.vs {
121 v := d.vs[i][len(d.vs[i])-1]
122 if v.Kind() == reflect.Ptr {
123 v = v.Elem()
124 }
125 var f reflect.Value
126 if v.Kind() == reflect.Slice {
127 v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
128 f = v.Index(v.Len() - 1)
129 someSliceExist = true
130 }
131 d.vs[i] = append(d.vs[i], f)
132 }
133 if !someSliceExist {
134 return fmt.Errorf("slice doesn't exist in any of %v places to unmarshal", len(d.vs))
135 }
136 }
137
138 switch tok := tok.(type) {
139 case string, json.Number, bool, nil:
140
141
142 for i := range d.vs {
143 v := d.vs[i][len(d.vs[i])-1]
144 if !v.IsValid() {
145 continue
146 }
147 err := unmarshalValue(tok, v)
148 if err != nil {
149 return err
150 }
151 }
152 d.popAllVs()
153
154 case json.Delim:
155 switch tok {
156 case '{':
157
158
159 d.pushState(tok)
160
161 frontier := make([]reflect.Value, len(d.vs))
162 for i := range d.vs {
163 v := d.vs[i][len(d.vs[i])-1]
164 frontier[i] = v
165
166 if v.Kind() == reflect.Ptr && v.IsNil() {
167 v.Set(reflect.New(v.Type().Elem()))
168 }
169 }
170
171
172 for len(frontier) > 0 {
173 v := frontier[0]
174 frontier = frontier[1:]
175 if v.Kind() == reflect.Ptr {
176 v = v.Elem()
177 }
178 if v.Kind() != reflect.Struct {
179 continue
180 }
181 for i := 0; i < v.NumField(); i++ {
182 if isGraphQLFragment(v.Type().Field(i)) || v.Type().Field(i).Anonymous {
183
184 d.vs = append(d.vs, []reflect.Value{v.Field(i)})
185 frontier = append(frontier, v.Field(i))
186 }
187 }
188 }
189 case '[':
190
191
192 d.pushState(tok)
193
194 for i := range d.vs {
195 v := d.vs[i][len(d.vs[i])-1]
196
197
198
199
200
201
202 if v.Kind() == reflect.Ptr {
203 v = v.Elem()
204 }
205 if v.Kind() != reflect.Slice {
206 continue
207 }
208 v.Set(reflect.MakeSlice(v.Type(), 0, 0))
209 }
210 case '}', ']':
211
212 d.popAllVs()
213 d.popState()
214 default:
215 return errors.New("unexpected delimiter in JSON input")
216 }
217 default:
218 return errors.New("unexpected token in JSON input")
219 }
220 }
221 return nil
222 }
223
224
225 func (d *decoder) pushState(s json.Delim) {
226 d.parseState = append(d.parseState, s)
227 }
228
229
230
231 func (d *decoder) popState() {
232 d.parseState = d.parseState[:len(d.parseState)-1]
233 }
234
235
236 func (d *decoder) state() json.Delim {
237 if len(d.parseState) == 0 {
238 return 0
239 }
240 return d.parseState[len(d.parseState)-1]
241 }
242
243
244 func (d *decoder) popAllVs() {
245 var nonEmpty [][]reflect.Value
246 for i := range d.vs {
247 d.vs[i] = d.vs[i][:len(d.vs[i])-1]
248 if len(d.vs[i]) > 0 {
249 nonEmpty = append(nonEmpty, d.vs[i])
250 }
251 }
252 d.vs = nonEmpty
253 }
254
255
256
257 func fieldByGraphQLName(v reflect.Value, name string) reflect.Value {
258 for i := 0; i < v.NumField(); i++ {
259 if v.Type().Field(i).PkgPath != "" {
260
261 continue
262 }
263 if hasGraphQLName(v.Type().Field(i), name) {
264 return v.Field(i)
265 }
266 }
267 return reflect.Value{}
268 }
269
270
271 func hasGraphQLName(f reflect.StructField, name string) bool {
272 value, ok := f.Tag.Lookup("graphql")
273 if !ok {
274
275
276 return strings.EqualFold(f.Name, name)
277 }
278 value = strings.TrimSpace(value)
279 if strings.HasPrefix(value, "...") {
280
281 return false
282 }
283
284
285 if i := strings.IndexAny(value, "(:@"); i != -1 {
286 value = value[:i]
287 }
288 return strings.TrimSpace(value) == name
289 }
290
291
292 func isGraphQLFragment(f reflect.StructField) bool {
293 value, ok := f.Tag.Lookup("graphql")
294 if !ok {
295 return false
296 }
297 value = strings.TrimSpace(value)
298 return strings.HasPrefix(value, "...")
299 }
300
301
302
303
304 func unmarshalValue(value json.Token, v reflect.Value) error {
305 b, err := json.Marshal(value)
306 if err != nil {
307 return err
308 }
309 return json.Unmarshal(b, v.Addr().Interface())
310 }
311
View as plain text