| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package json_test |
| |
| import ( |
| "strings" |
| "testing" |
| "unicode/utf8" |
| |
| "google.golang.org/protobuf/internal/encoding/json" |
| ) |
| |
| type R struct { |
| // T is expected Type returned from calling Decoder.Read. |
| T json.Type |
| // E is expected error substring from calling Decoder.Read if set. |
| E string |
| // V is expected value from calling |
| // Value.{Bool()|Float()|Int()|Uint()|String()} depending on type. |
| V interface{} |
| // VE is expected error substring from calling |
| // Value.{Bool()|Float()|Int()|Uint()|String()} depending on type if set. |
| VE string |
| } |
| |
| func TestDecoder(t *testing.T) { |
| const space = " \n\r\t" |
| |
| tests := []struct { |
| input string |
| // want is a list of expected values returned from calling |
| // Decoder.Read. An item makes the test code invoke |
| // Decoder.Read and compare against R.T and R.E. For Bool, |
| // Number and String tokens, it invokes the corresponding getter method |
| // and compares the returned value against R.V or R.VE if it returned an |
| // error. |
| want []R |
| }{ |
| { |
| input: ``, |
| want: []R{{T: json.EOF}}, |
| }, |
| { |
| input: space, |
| want: []R{{T: json.EOF}}, |
| }, |
| { |
| // Calling Read after EOF will keep returning EOF for |
| // succeeding Read calls. |
| input: space, |
| want: []R{ |
| {T: json.EOF}, |
| {T: json.EOF}, |
| {T: json.EOF}, |
| }, |
| }, |
| |
| // JSON literals. |
| { |
| input: space + `null` + space, |
| want: []R{ |
| {T: json.Null}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `true` + space, |
| want: []R{ |
| {T: json.Bool, V: true}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `false` + space, |
| want: []R{ |
| {T: json.Bool, V: false}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Error returned will produce the same error again. |
| input: space + `foo` + space, |
| want: []R{ |
| {E: `invalid value foo`}, |
| {E: `invalid value foo`}, |
| }, |
| }, |
| |
| // JSON strings. |
| { |
| input: space + `""` + space, |
| want: []R{ |
| {T: json.String, V: ""}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `"hello"` + space, |
| want: []R{ |
| {T: json.String, V: "hello"}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `"hello`, |
| want: []R{{E: `unexpected EOF`}}, |
| }, |
| { |
| input: "\"\x00\"", |
| want: []R{{E: `invalid character '\x00' in string`}}, |
| }, |
| { |
| input: "\"\u0031\u0032\"", |
| want: []R{ |
| {T: json.String, V: "12"}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Invalid UTF-8 error is returned in ReadString instead of Read. |
| input: "\"\xff\"", |
| want: []R{{E: `syntax error (line 1:1): invalid UTF-8 in string`}}, |
| }, |
| { |
| input: `"` + string(utf8.RuneError) + `"`, |
| want: []R{ |
| {T: json.String, V: string(utf8.RuneError)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `"\uFFFD"`, |
| want: []R{ |
| {T: json.String, V: string(utf8.RuneError)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `"\x"`, |
| want: []R{{E: `invalid escape code "\\x" in string`}}, |
| }, |
| { |
| input: `"\uXXXX"`, |
| want: []R{{E: `invalid escape code "\\uXXXX" in string`}}, |
| }, |
| { |
| input: `"\uDEAD"`, // unmatched surrogate pair |
| want: []R{{E: `unexpected EOF`}}, |
| }, |
| { |
| input: `"\uDEAD\uBEEF"`, // invalid surrogate half |
| want: []R{{E: `invalid escape code "\\uBEEF" in string`}}, |
| }, |
| { |
| input: `"\uD800\udead"`, // valid surrogate pair |
| want: []R{ |
| {T: json.String, V: `𐊭`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `"\u0000\"\\\/\b\f\n\r\t"`, |
| want: []R{ |
| {T: json.String, V: "\u0000\"\\/\b\f\n\r\t"}, |
| {T: json.EOF}, |
| }, |
| }, |
| |
| // Invalid JSON numbers. |
| { |
| input: `-`, |
| want: []R{{E: `invalid number -`}}, |
| }, |
| { |
| input: `+0`, |
| want: []R{{E: `invalid value +0`}}, |
| }, |
| { |
| input: `-+`, |
| want: []R{{E: `invalid number -+`}}, |
| }, |
| { |
| input: `0.`, |
| want: []R{{E: `invalid number 0.`}}, |
| }, |
| { |
| input: `.1`, |
| want: []R{{E: `invalid value .1`}}, |
| }, |
| { |
| input: `1.0.1`, |
| want: []R{{E: `invalid number 1.0.1`}}, |
| }, |
| { |
| input: `1..1`, |
| want: []R{{E: `invalid number 1..1`}}, |
| }, |
| { |
| input: `-1-2`, |
| want: []R{{E: `invalid number -1-2`}}, |
| }, |
| { |
| input: `01`, |
| want: []R{{E: `invalid number 01`}}, |
| }, |
| { |
| input: `1e`, |
| want: []R{{E: `invalid number 1e`}}, |
| }, |
| { |
| input: `1e1.2`, |
| want: []R{{E: `invalid number 1e1.2`}}, |
| }, |
| { |
| input: `1Ee`, |
| want: []R{{E: `invalid number 1Ee`}}, |
| }, |
| { |
| input: `1.e1`, |
| want: []R{{E: `invalid number 1.e1`}}, |
| }, |
| { |
| input: `1.e+`, |
| want: []R{{E: `invalid number 1.e+`}}, |
| }, |
| { |
| input: `1e+-2`, |
| want: []R{{E: `invalid number 1e+-2`}}, |
| }, |
| { |
| input: `1e--2`, |
| want: []R{{E: `invalid number 1e--2`}}, |
| }, |
| { |
| input: `1.0true`, |
| want: []R{{E: `invalid number 1.0true`}}, |
| }, |
| |
| // JSON numbers as floating point. |
| { |
| input: space + `0.0` + space, |
| want: []R{ |
| {T: json.Number, V: float32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `0` + space, |
| want: []R{ |
| {T: json.Number, V: float32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `-0` + space, |
| want: []R{ |
| {T: json.Number, V: float32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-1.02`, |
| want: []R{ |
| {T: json.Number, V: float32(-1.02)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `1.020000`, |
| want: []R{ |
| {T: json.Number, V: float32(1.02)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-1.0e0`, |
| want: []R{ |
| {T: json.Number, V: float32(-1)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `1.0e-000`, |
| want: []R{ |
| {T: json.Number, V: float32(1)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `1e+00`, |
| want: []R{ |
| {T: json.Number, V: float32(1)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `1.02e3`, |
| want: []R{ |
| {T: json.Number, V: float32(1.02e3)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-1.02E03`, |
| want: []R{ |
| {T: json.Number, V: float32(-1.02e3)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `1.0200e+3`, |
| want: []R{ |
| {T: json.Number, V: float32(1.02e3)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-1.0200E+03`, |
| want: []R{ |
| {T: json.Number, V: float32(-1.02e3)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `1.0200e-3`, |
| want: []R{ |
| {T: json.Number, V: float32(1.02e-3)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-1.0200E-03`, |
| want: []R{ |
| {T: json.Number, V: float32(-1.02e-3)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds max float32 limit, but should be ok for float64. |
| input: `3.4e39`, |
| want: []R{ |
| {T: json.Number, V: float64(3.4e39)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds max float32 limit. |
| input: `3.4e39`, |
| want: []R{ |
| {T: json.Number, V: float32(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Less than negative max float32 limit. |
| input: `-3.4e39`, |
| want: []R{ |
| {T: json.Number, V: float32(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds max float64 limit. |
| input: `1.79e+309`, |
| want: []R{ |
| {T: json.Number, V: float64(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Less than negative max float64 limit. |
| input: `-1.79e+309`, |
| want: []R{ |
| {T: json.Number, V: float64(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| |
| // JSON numbers as signed integers. |
| { |
| input: space + `0` + space, |
| want: []R{ |
| {T: json.Number, V: int32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `-0` + space, |
| want: []R{ |
| {T: json.Number, V: int32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Fractional part equals 0 is ok. |
| input: `1.00000`, |
| want: []R{ |
| {T: json.Number, V: int32(1)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Fractional part not equals 0 returns error. |
| input: `1.0000000001`, |
| want: []R{ |
| {T: json.Number, V: int32(0), VE: `cannot convert 1.0000000001 to integer`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `0e0`, |
| want: []R{ |
| {T: json.Number, V: int32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `0.0E0`, |
| want: []R{ |
| {T: json.Number, V: int32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `0.0E10`, |
| want: []R{ |
| {T: json.Number, V: int32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-1`, |
| want: []R{ |
| {T: json.Number, V: int32(-1)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `1.0e+0`, |
| want: []R{ |
| {T: json.Number, V: int32(1)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-1E-0`, |
| want: []R{ |
| {T: json.Number, V: int32(-1)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `1E1`, |
| want: []R{ |
| {T: json.Number, V: int32(10)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-100.00e-02`, |
| want: []R{ |
| {T: json.Number, V: int32(-1)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `0.1200E+02`, |
| want: []R{ |
| {T: json.Number, V: int64(12)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `0.012e2`, |
| want: []R{ |
| {T: json.Number, V: int32(0), VE: `cannot convert 0.012e2 to integer`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `12e-2`, |
| want: []R{ |
| {T: json.Number, V: int32(0), VE: `cannot convert 12e-2 to integer`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds math.MaxInt32. |
| input: `2147483648`, |
| want: []R{ |
| {T: json.Number, V: int32(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds math.MinInt32. |
| input: `-2147483649`, |
| want: []R{ |
| {T: json.Number, V: int32(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds math.MaxInt32, but ok for int64. |
| input: `2147483648`, |
| want: []R{ |
| {T: json.Number, V: int64(2147483648)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds math.MinInt32, but ok for int64. |
| input: `-2147483649`, |
| want: []R{ |
| {T: json.Number, V: int64(-2147483649)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds math.MaxInt64. |
| input: `9223372036854775808`, |
| want: []R{ |
| {T: json.Number, V: int64(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds math.MinInt64. |
| input: `-9223372036854775809`, |
| want: []R{ |
| {T: json.Number, V: int64(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| |
| // JSON numbers as unsigned integers. |
| { |
| input: space + `0` + space, |
| want: []R{ |
| {T: json.Number, V: uint32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `-0` + space, |
| want: []R{ |
| {T: json.Number, V: uint32(0)}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `-1`, |
| want: []R{ |
| {T: json.Number, V: uint32(0), VE: `invalid syntax`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds math.MaxUint32. |
| input: `4294967296`, |
| want: []R{ |
| {T: json.Number, V: uint32(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| // Exceeds math.MaxUint64. |
| input: `18446744073709551616`, |
| want: []R{ |
| {T: json.Number, V: uint64(0), VE: `value out of range`}, |
| {T: json.EOF}, |
| }, |
| }, |
| |
| // JSON sequence of values. |
| { |
| input: `true null`, |
| want: []R{ |
| {T: json.Bool, V: true}, |
| {E: `unexpected value null`}, |
| }, |
| }, |
| { |
| input: "null false", |
| want: []R{ |
| {T: json.Null}, |
| {E: `unexpected value false`}, |
| }, |
| }, |
| { |
| input: `true,false`, |
| want: []R{ |
| {T: json.Bool, V: true}, |
| {E: `unexpected character ,`}, |
| }, |
| }, |
| { |
| input: `47"hello"`, |
| want: []R{ |
| {T: json.Number, V: int32(47)}, |
| {E: `unexpected value "hello"`}, |
| }, |
| }, |
| { |
| input: `47 "hello"`, |
| want: []R{ |
| {T: json.Number, V: int32(47)}, |
| {E: `unexpected value "hello"`}, |
| }, |
| }, |
| { |
| input: `true 42`, |
| want: []R{ |
| {T: json.Bool, V: true}, |
| {E: `unexpected value 42`}, |
| }, |
| }, |
| |
| // JSON arrays. |
| { |
| input: space + `[]` + space, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.EndArray}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `[` + space + `]` + space, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.EndArray}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `[` + space, |
| want: []R{ |
| {T: json.StartArray}, |
| {E: `unexpected EOF`}, |
| }, |
| }, |
| { |
| input: space + `]` + space, |
| want: []R{{E: `unexpected character ]`}}, |
| }, |
| { |
| input: `[null,true,false, 1e1, "hello" ]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.Null}, |
| {T: json.Bool, V: true}, |
| {T: json.Bool, V: false}, |
| {T: json.Number, V: int32(10)}, |
| {T: json.String, V: "hello"}, |
| {T: json.EndArray}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `[` + space + `true` + space + `,` + space + `"hello"` + space + `]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.Bool, V: true}, |
| {T: json.String, V: "hello"}, |
| {T: json.EndArray}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `[` + space + `true` + space + `,` + space + `]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.Bool, V: true}, |
| {E: `unexpected character ]`}, |
| }, |
| }, |
| { |
| input: `[` + space + `false` + space + `]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.Bool, V: false}, |
| {T: json.EndArray}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `[` + space + `1` + space + `0` + space + `]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.Number, V: int64(1)}, |
| {E: `unexpected value 0`}, |
| }, |
| }, |
| { |
| input: `[null`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.Null}, |
| {E: `unexpected EOF`}, |
| }, |
| }, |
| { |
| input: `[foo]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {E: `invalid value foo`}, |
| }, |
| }, |
| { |
| input: `[{}, "hello", [true, false], null]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.StartObject}, |
| {T: json.EndObject}, |
| {T: json.String, V: "hello"}, |
| {T: json.StartArray}, |
| {T: json.Bool, V: true}, |
| {T: json.Bool, V: false}, |
| {T: json.EndArray}, |
| {T: json.Null}, |
| {T: json.EndArray}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `[{ ]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.StartObject}, |
| {E: `unexpected character ]`}, |
| }, |
| }, |
| { |
| input: `[[ ]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.StartArray}, |
| {T: json.EndArray}, |
| {E: `unexpected EOF`}, |
| }, |
| }, |
| { |
| input: `[,]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {E: `unexpected character ,`}, |
| }, |
| }, |
| { |
| input: `[true "hello"]`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.Bool, V: true}, |
| {E: `unexpected value "hello"`}, |
| }, |
| }, |
| { |
| input: `[] null`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.EndArray}, |
| {E: `unexpected value null`}, |
| }, |
| }, |
| { |
| input: `true []`, |
| want: []R{ |
| {T: json.Bool, V: true}, |
| {E: `unexpected character [`}, |
| }, |
| }, |
| |
| // JSON objects. |
| { |
| input: space + `{}` + space, |
| want: []R{ |
| {T: json.StartObject}, |
| {T: json.EndObject}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `{` + space + `}` + space, |
| want: []R{ |
| {T: json.StartObject}, |
| {T: json.EndObject}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: space + `{` + space, |
| want: []R{ |
| {T: json.StartObject}, |
| {E: `unexpected EOF`}, |
| }, |
| }, |
| { |
| input: space + `}` + space, |
| want: []R{{E: `unexpected character }`}}, |
| }, |
| { |
| input: `{` + space + `null` + space + `}`, |
| want: []R{ |
| {T: json.StartObject}, |
| {E: `unexpected value null`}, |
| }, |
| }, |
| { |
| input: `{[]}`, |
| want: []R{ |
| {T: json.StartObject}, |
| {E: `unexpected character [`}, |
| }, |
| }, |
| { |
| input: `{,}`, |
| want: []R{ |
| {T: json.StartObject}, |
| {E: `unexpected character ,`}, |
| }, |
| }, |
| { |
| input: `{"345678"}`, |
| want: []R{ |
| {T: json.StartObject}, |
| {E: `unexpected character }, missing ":" after object name`}, |
| }, |
| }, |
| { |
| input: `{` + space + `"hello"` + space + `:` + space + `"world"` + space + `}`, |
| want: []R{ |
| {T: json.StartObject}, |
| {T: json.Name, V: "hello"}, |
| {T: json.String, V: "world"}, |
| {T: json.EndObject}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `{"hello" "world"}`, |
| want: []R{ |
| {T: json.StartObject}, |
| {E: `unexpected character ", missing ":" after object name`}, |
| }, |
| }, |
| { |
| input: `{"hello":`, |
| want: []R{ |
| {T: json.StartObject}, |
| {T: json.Name, V: "hello"}, |
| {E: `unexpected EOF`}, |
| }, |
| }, |
| { |
| input: `{"hello":"world"`, |
| want: []R{ |
| {T: json.StartObject}, |
| {T: json.Name, V: "hello"}, |
| {T: json.String, V: "world"}, |
| {E: `unexpected EOF`}, |
| }, |
| }, |
| { |
| input: `{"hello":"world",`, |
| want: []R{ |
| {T: json.StartObject}, |
| {T: json.Name, V: "hello"}, |
| {T: json.String, V: "world"}, |
| {E: `unexpected EOF`}, |
| }, |
| }, |
| { |
| input: `{"34":"89",}`, |
| want: []R{ |
| {T: json.StartObject}, |
| {T: json.Name, V: "34"}, |
| {T: json.String, V: "89"}, |
| {E: `syntax error (line 1:12): unexpected character }`}, |
| }, |
| }, |
| { |
| input: `{ |
| "number": 123e2, |
| "bool" : false, |
| "object": {"string": "world"}, |
| "null" : null, |
| "array" : [1.01, "hello", true], |
| "string": "hello" |
| }`, |
| want: []R{ |
| {T: json.StartObject}, |
| |
| {T: json.Name, V: "number"}, |
| {T: json.Number, V: int32(12300)}, |
| |
| {T: json.Name, V: "bool"}, |
| {T: json.Bool, V: false}, |
| |
| {T: json.Name, V: "object"}, |
| {T: json.StartObject}, |
| {T: json.Name, V: "string"}, |
| {T: json.String, V: "world"}, |
| {T: json.EndObject}, |
| |
| {T: json.Name, V: "null"}, |
| {T: json.Null}, |
| |
| {T: json.Name, V: "array"}, |
| {T: json.StartArray}, |
| {T: json.Number, V: float32(1.01)}, |
| {T: json.String, V: "hello"}, |
| {T: json.Bool, V: true}, |
| {T: json.EndArray}, |
| |
| {T: json.Name, V: "string"}, |
| {T: json.String, V: "hello"}, |
| |
| {T: json.EndObject}, |
| {T: json.EOF}, |
| }, |
| }, |
| { |
| input: `[ |
| {"object": {"number": 47}}, |
| ["list"], |
| null |
| ]`, |
| want: []R{ |
| {T: json.StartArray}, |
| |
| {T: json.StartObject}, |
| {T: json.Name, V: "object"}, |
| {T: json.StartObject}, |
| {T: json.Name, V: "number"}, |
| {T: json.Number, V: uint32(47)}, |
| {T: json.EndObject}, |
| {T: json.EndObject}, |
| |
| {T: json.StartArray}, |
| {T: json.String, V: "list"}, |
| {T: json.EndArray}, |
| |
| {T: json.Null}, |
| |
| {T: json.EndArray}, |
| {T: json.EOF}, |
| }, |
| }, |
| |
| // Tests for line and column info. |
| { |
| input: `12345678 x`, |
| want: []R{ |
| {T: json.Number, V: int64(12345678)}, |
| {E: `syntax error (line 1:10): invalid value x`}, |
| }, |
| }, |
| { |
| input: "\ntrue\n x", |
| want: []R{ |
| {T: json.Bool, V: true}, |
| {E: `syntax error (line 3:4): invalid value x`}, |
| }, |
| }, |
| { |
| input: `"💩"x`, |
| want: []R{ |
| {T: json.String, V: "💩"}, |
| {E: `syntax error (line 1:4): invalid value x`}, |
| }, |
| }, |
| { |
| input: "\n\n[\"🔥🔥🔥\"x", |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.String, V: "🔥🔥🔥"}, |
| {E: `syntax error (line 3:7): invalid value x`}, |
| }, |
| }, |
| { |
| // Multi-rune emojis. |
| input: `["👍🏻👍🏿"x`, |
| want: []R{ |
| {T: json.StartArray}, |
| {T: json.String, V: "👍🏻👍🏿"}, |
| {E: `syntax error (line 1:8): invalid value x`}, |
| }, |
| }, |
| { |
| input: `{ |
| "45678":-1 |
| }`, |
| want: []R{ |
| {T: json.StartObject}, |
| {T: json.Name, V: "45678"}, |
| {T: json.Number, V: uint64(1), VE: "error (line 2:11)"}, |
| }, |
| }, |
| } |
| |
| for _, tc := range tests { |
| tc := tc |
| t.Run("", func(t *testing.T) { |
| dec := json.NewDecoder([]byte(tc.input)) |
| for i, want := range tc.want { |
| typ := dec.Peek() |
| if typ != want.T { |
| t.Errorf("input: %v\nPeek() got %v want %v", tc.input, typ, want.T) |
| } |
| value, err := dec.Read() |
| if err != nil { |
| if want.E == "" { |
| t.Errorf("input: %v\nRead() got unexpected error: %v", tc.input, err) |
| |
| } else if !strings.Contains(err.Error(), want.E) { |
| t.Errorf("input: %v\nRead() got %q, want %q", tc.input, err, want.E) |
| } |
| } else { |
| if want.E != "" { |
| t.Errorf("input: %v\nRead() got nil error, want %q", tc.input, want.E) |
| } |
| } |
| token := value.Type() |
| if token != want.T { |
| t.Errorf("input: %v\nRead() got %v, want %v", tc.input, token, want.T) |
| break |
| } |
| checkValue(t, value, i, want) |
| } |
| }) |
| } |
| } |
| |
| func checkValue(t *testing.T, value json.Value, wantIdx int, want R) { |
| var got interface{} |
| var err error |
| switch value.Type() { |
| case json.Bool: |
| got, err = value.Bool() |
| case json.String: |
| got = value.String() |
| case json.Name: |
| got, err = value.Name() |
| case json.Number: |
| switch want.V.(type) { |
| case float32: |
| got, err = value.Float(32) |
| got = float32(got.(float64)) |
| case float64: |
| got, err = value.Float(64) |
| case int32: |
| got, err = value.Int(32) |
| got = int32(got.(int64)) |
| case int64: |
| got, err = value.Int(64) |
| case uint32: |
| got, err = value.Uint(32) |
| got = uint32(got.(uint64)) |
| case uint64: |
| got, err = value.Uint(64) |
| } |
| default: |
| return |
| } |
| |
| if err != nil { |
| if want.VE == "" { |
| t.Errorf("want%d: %v got unexpected error: %v", wantIdx, value, err) |
| } else if !strings.Contains(err.Error(), want.VE) { |
| t.Errorf("want#%d: %v got %q, want %q", wantIdx, value, err, want.VE) |
| } |
| return |
| } else { |
| if want.VE != "" { |
| t.Errorf("want#%d: %v got nil error, want %q", wantIdx, value, want.VE) |
| return |
| } |
| } |
| |
| if got != want.V { |
| t.Errorf("want#%d: %v got %v, want %v", wantIdx, value, got, want.V) |
| } |
| } |
| |
| func TestClone(t *testing.T) { |
| input := `{"outer":{"str":"hello", "number": 123}}` |
| dec := json.NewDecoder([]byte(input)) |
| |
| // Clone at the start should produce the same reads as the original. |
| clone := dec.Clone() |
| compareDecoders(t, dec, clone) |
| |
| // Advance to inner object, clone and compare again. |
| dec.Read() // Read StartObject. |
| dec.Read() // Read Name. |
| clone = dec.Clone() |
| compareDecoders(t, dec, clone) |
| } |
| |
| func compareDecoders(t *testing.T, d1 *json.Decoder, d2 *json.Decoder) { |
| for { |
| v1, err1 := d1.Read() |
| v2, err2 := d2.Read() |
| if v1.Type() != v2.Type() { |
| t.Errorf("cloned decoder: got Type %v, want %v", v2.Type(), v1.Type()) |
| } |
| if v1.Raw() != v2.Raw() { |
| t.Errorf("cloned decoder: got Raw %v, want %v", v2.Raw(), v1.Raw()) |
| } |
| if err1 != err2 { |
| t.Errorf("cloned decoder: got error %v, want %v", err2, err1) |
| } |
| if v1.Type() == json.EOF { |
| break |
| } |
| } |
| } |