@@ -20,10 +20,12 @@ import (
20
20
"bufio"
21
21
"bytes"
22
22
"encoding/json"
23
+ "errors"
23
24
"fmt"
24
25
"io"
25
26
"strings"
26
27
"unicode"
28
+ "unicode/utf8"
27
29
28
30
jsonutil "k8s.io/apimachinery/pkg/util/json"
29
31
@@ -92,7 +94,7 @@ func UnmarshalStrict(data []byte, v interface{}) error {
92
94
// YAML decoding path is not used (so that error messages are
93
95
// JSON specific).
94
96
func ToJSON (data []byte ) ([]byte , error ) {
95
- if hasJSONPrefix (data ) {
97
+ if IsJSONBuffer (data ) {
96
98
return data , nil
97
99
}
98
100
return yaml .YAMLToJSON (data )
@@ -102,7 +104,8 @@ func ToJSON(data []byte) ([]byte, error) {
102
104
// separating individual documents. It first converts the YAML
103
105
// body to JSON, then unmarshals the JSON.
104
106
type YAMLToJSONDecoder struct {
105
- reader Reader
107
+ reader Reader
108
+ inputOffset int
106
109
}
107
110
108
111
// NewYAMLToJSONDecoder decodes YAML documents from the provided
@@ -121,7 +124,7 @@ func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder {
121
124
// yaml.Unmarshal.
122
125
func (d * YAMLToJSONDecoder ) Decode (into interface {}) error {
123
126
bytes , err := d .reader .Read ()
124
- if err != nil && err != io .EOF {
127
+ if err != nil && err != io .EOF { //nolint:errorlint
125
128
return err
126
129
}
127
130
@@ -131,9 +134,14 @@ func (d *YAMLToJSONDecoder) Decode(into interface{}) error {
131
134
return YAMLSyntaxError {err }
132
135
}
133
136
}
137
+ d .inputOffset += len (bytes )
134
138
return err
135
139
}
136
140
141
+ func (d * YAMLToJSONDecoder ) InputOffset () int {
142
+ return d .inputOffset
143
+ }
144
+
137
145
// YAMLDecoder reads chunks of objects and returns ErrShortBuffer if
138
146
// the data is not sufficient.
139
147
type YAMLDecoder struct {
@@ -229,18 +237,20 @@ func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err
229
237
return 0 , nil , nil
230
238
}
231
239
232
- // decoder is a convenience interface for Decode.
233
- type decoder interface {
234
- Decode (into interface {}) error
235
- }
236
-
237
- // YAMLOrJSONDecoder attempts to decode a stream of JSON documents or
238
- // YAML documents by sniffing for a leading { character.
240
+ // YAMLOrJSONDecoder attempts to decode a stream of JSON or YAML documents.
241
+ // While JSON is YAML, the way Go's JSON decode defines a multi-document stream
242
+ // is a series of JSON objects (e.g. {}{}), but YAML defines a multi-document
243
+ // stream as a series of documents separated by "---".
244
+ //
245
+ // This decoder will attempt to decode the stream as JSON first, and if that
246
+ // fails, it will switch to YAML. Once it determines the stream is JSON (by
247
+ // finding a non-YAML-delimited series of objects), it will not switch to YAML.
248
+ // Once it switches to YAML it will not switch back to JSON.
239
249
type YAMLOrJSONDecoder struct {
240
- r io. Reader
241
- bufferSize int
242
-
243
- decoder decoder
250
+ json * json. Decoder
251
+ yaml * YAMLToJSONDecoder
252
+ stream * StreamReader
253
+ count int // how many objects have been decoded
244
254
}
245
255
246
256
type JSONSyntaxError struct {
@@ -265,31 +275,108 @@ func (e YAMLSyntaxError) Error() string {
265
275
// how far into the stream the decoder will look to figure out whether this
266
276
// is a JSON stream (has whitespace followed by an open brace).
267
277
func NewYAMLOrJSONDecoder (r io.Reader , bufferSize int ) * YAMLOrJSONDecoder {
268
- return & YAMLOrJSONDecoder {
269
- r : r ,
270
- bufferSize : bufferSize ,
278
+ d := & YAMLOrJSONDecoder {}
279
+
280
+ reader , _ , mightBeJSON := GuessJSONStream (r , bufferSize )
281
+ d .stream = reader
282
+ if mightBeJSON {
283
+ d .json = json .NewDecoder (reader )
284
+ } else {
285
+ d .yaml = NewYAMLToJSONDecoder (reader )
271
286
}
287
+ return d
272
288
}
273
289
274
290
// Decode unmarshals the next object from the underlying stream into the
275
291
// provide object, or returns an error.
276
292
func (d * YAMLOrJSONDecoder ) Decode (into interface {}) error {
277
- if d .decoder == nil {
278
- buffer , _ , isJSON := GuessJSONStream (d .r , d .bufferSize )
279
- if isJSON {
280
- d .decoder = json .NewDecoder (buffer )
293
+ // Because we don't know if this is a JSON or YAML stream, a failure from
294
+ // both decoders is ambiguous. When in doubt, it will return the error from
295
+ // the JSON decoder. Unfortunately, this means that if the first document
296
+ // is invalid YAML, the error won't be awesome.
297
+ // TODO: the errors from YAML are not great, we could improve them a lot.
298
+ var firstErr error
299
+ if d .json != nil {
300
+ err := d .json .Decode (into )
301
+ if err == nil {
302
+ d .stream .Consume (int (d .json .InputOffset ()) - d .stream .Consumed ())
303
+ d .count ++
304
+ return nil
305
+ }
306
+ if err == io .EOF { //nolint:errorlint
307
+ return err
308
+ }
309
+ var syntax * json.SyntaxError
310
+ if ok := errors .As (err , & syntax ); ok {
311
+ firstErr = JSONSyntaxError {
312
+ Offset : syntax .Offset ,
313
+ Err : syntax ,
314
+ }
281
315
} else {
282
- d .decoder = NewYAMLToJSONDecoder (buffer )
316
+ firstErr = err
317
+ }
318
+ if d .count > 1 {
319
+ // If we found 0 or 1 JSON object(s), this stream is still
320
+ // ambiguous. But if we found more than 1 JSON object, then this
321
+ // is an unambiguous JSON stream, and we should not switch to YAML.
322
+ return err
323
+ }
324
+ // If JSON decoding hits the end of one object and then fails on the
325
+ // next, it leaves any leading whitespace in the buffer, which can
326
+ // confuse the YAML decoder. We just eat any whitespace we find, up to
327
+ // and including the first newline.
328
+ d .stream .Rewind ()
329
+ if err := d .consumeWhitespace (); err == nil {
330
+ d .yaml = NewYAMLToJSONDecoder (d .stream )
331
+ }
332
+ d .json = nil
333
+ }
334
+ if d .yaml != nil {
335
+ err := d .yaml .Decode (into )
336
+ if err == nil {
337
+ d .stream .Consume (d .yaml .InputOffset () - d .stream .Consumed ())
338
+ d .count ++
339
+ return nil
340
+ }
341
+ if err == io .EOF { //nolint:errorlint
342
+ return err
343
+ }
344
+ if firstErr == nil {
345
+ firstErr = err
283
346
}
284
347
}
285
- err := d .decoder .Decode (into )
286
- if syntax , ok := err .(* json.SyntaxError ); ok {
287
- return JSONSyntaxError {
288
- Offset : syntax .Offset ,
289
- Err : syntax ,
348
+ if firstErr != nil {
349
+ return firstErr
350
+ }
351
+ return fmt .Errorf ("decoding failed as both JSON and YAML" )
352
+ }
353
+
354
+ func (d * YAMLOrJSONDecoder ) consumeWhitespace () error {
355
+ consumed := 0
356
+ for {
357
+ buf , err := d .stream .ReadN (4 )
358
+ if err != nil && err == io .EOF { //nolint:errorlint
359
+ return err
360
+ }
361
+ r , sz := utf8 .DecodeRune (buf )
362
+ if r == utf8 .RuneError || sz == 0 {
363
+ return fmt .Errorf ("invalid utf8 rune" )
364
+ }
365
+ d .stream .RewindN (len (buf ) - sz )
366
+ if ! unicode .IsSpace (r ) {
367
+ d .stream .RewindN (sz )
368
+ d .stream .Consume (consumed )
369
+ return nil
370
+ }
371
+ if r == '\n' {
372
+ d .stream .Consume (consumed )
373
+ return nil
374
+ }
375
+ if err == io .EOF { //nolint:errorlint
376
+ break
290
377
}
291
378
}
292
- return err
379
+ return io . EOF
293
380
}
294
381
295
382
type Reader interface {
@@ -311,7 +398,7 @@ func (r *YAMLReader) Read() ([]byte, error) {
311
398
var buffer bytes.Buffer
312
399
for {
313
400
line , err := r .reader .Read ()
314
- if err != nil && err != io .EOF {
401
+ if err != nil && err != io .EOF { //nolint:errorlint
315
402
return nil , err
316
403
}
317
404
@@ -329,11 +416,11 @@ func (r *YAMLReader) Read() ([]byte, error) {
329
416
if buffer .Len () != 0 {
330
417
return buffer .Bytes (), nil
331
418
}
332
- if err == io .EOF {
419
+ if err == io .EOF { //nolint:errorlint
333
420
return nil , err
334
421
}
335
422
}
336
- if err == io .EOF {
423
+ if err == io .EOF { //nolint:errorlint
337
424
if buffer .Len () != 0 {
338
425
// If we're at EOF, we have a final, non-terminated line. Return it.
339
426
return buffer .Bytes (), nil
@@ -369,26 +456,20 @@ func (r *LineReader) Read() ([]byte, error) {
369
456
// GuessJSONStream scans the provided reader up to size, looking
370
457
// for an open brace indicating this is JSON. It will return the
371
458
// bufio.Reader it creates for the consumer.
372
- func GuessJSONStream (r io.Reader , size int ) (io. Reader , []byte , bool ) {
373
- buffer := bufio . NewReaderSize (r , size )
459
+ func GuessJSONStream (r io.Reader , size int ) (* StreamReader , []byte , bool ) {
460
+ buffer := NewStreamReader (r , size )
374
461
b , _ := buffer .Peek (size )
375
- return buffer , b , hasJSONPrefix (b )
462
+ return buffer , b , IsJSONBuffer (b )
376
463
}
377
464
378
465
// IsJSONBuffer scans the provided buffer, looking
379
466
// for an open brace indicating this is JSON.
380
467
func IsJSONBuffer (buf []byte ) bool {
381
- return hasJSONPrefix (buf )
468
+ return hasPrefix (buf , jsonPrefix )
382
469
}
383
470
384
471
var jsonPrefix = []byte ("{" )
385
472
386
- // hasJSONPrefix returns true if the provided buffer appears to start with
387
- // a JSON open brace.
388
- func hasJSONPrefix (buf []byte ) bool {
389
- return hasPrefix (buf , jsonPrefix )
390
- }
391
-
392
473
// Return true if the first non-whitespace bytes in buf is
393
474
// prefix.
394
475
func hasPrefix (buf []byte , prefix []byte ) bool {
0 commit comments