diff --git a/decode.go b/decode.go index 005431f..f5700cc 100644 --- a/decode.go +++ b/decode.go @@ -113,21 +113,26 @@ type UnmarshalTypeError struct { } func (e *UnmarshalTypeError) Error() string { - return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() + return fmt.Sprintf("json: %s at offset %d", e.message(), e.Offset) +} + +func (e *UnmarshalTypeError) message() string { + return fmt.Sprintf("cannot unmarshal %s into Go value of type %s", e.Value, e.Type) } // An UnmarshalFieldError describes a JSON object key that // led to an unexported (and therefore unwritable) struct field. // (No longer used; kept for compatibility.) -type UnmarshalFieldError struct { - Key string - Type reflect.Type - Field reflect.StructField -} - -func (e *UnmarshalFieldError) Error() string { - return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() -} +//type UnmarshalFieldError struct { +// Key string +// Type reflect.Type +// Field reflect.StructField +// Offset int64 // error occurred after reading Offset bytes +//} +// +//func (e *UnmarshalFieldError) Error() string { +// return fmt.Sprintf("json: cannot unmarshal object key %s into unexported field %s of type %s at offset %d", strconv.Quote(e.Key), e.Field.Name, e.Type, e.Offset) +//} // An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. // (The argument to Unmarshal must be a non-nil pointer.) diff --git a/decode_test.go b/decode_test.go index 9c33775..bf10130 100644 --- a/decode_test.go +++ b/decode_test.go @@ -288,7 +288,7 @@ var unmarshalTests = []unmarshalTest{ // syntax errors {in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}}, - {in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}}, + {in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element (expecting comma or ']')", 9}}, {in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true}, // raw value errors @@ -1318,7 +1318,7 @@ func TestInvalidUnmarshal(t *testing.T) { continue } if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) + t.Errorf("got %q; want %q", got, tt.want) } } } @@ -1330,19 +1330,19 @@ var invalidUnmarshalTextTests = []struct { {nil, "json: Unmarshal(nil)"}, {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, {(*int)(nil), "json: Unmarshal(nil *int)"}, - {new(net.IP), "json: cannot unmarshal string into Go value of type *net.IP"}, + {new(net.IP), "json: cannot unmarshal string into Go value of type *net.IP at offset 3"}, } func TestInvalidUnmarshalText(t *testing.T) { buf := []byte(`123`) - for _, tt := range invalidUnmarshalTextTests { + for i, tt := range invalidUnmarshalTextTests { err := Unmarshal(buf, tt.v) if err == nil { - t.Errorf("Unmarshal expecting error, got nil") + t.Errorf("#%d Unmarshal expecting error, got nil", i) continue } if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) + t.Errorf("#%d got %q; want %q", i, got, tt.want) } } } @@ -1356,6 +1356,6 @@ func TestDecodeSingleQuoteStringInterface(t *testing.T) { } want := map[string]interface{}{"key": "value"} if !reflect.DeepEqual(got, want) { - t.Errorf("Unmarshal = %q; want %q", got, want) + t.Errorf("got %q; want %q", got, want) } } diff --git a/json5_test.go b/json5_test.go index 4e8eeb1..a52c5f5 100644 --- a/json5_test.go +++ b/json5_test.go @@ -2,10 +2,10 @@ package json5 import ( "encoding/json" + "errors" "io/ioutil" "os" "path/filepath" - "strings" "testing" "github.com/kylelemons/godebug/pretty" @@ -13,7 +13,7 @@ import ( ) type ErrorSpec struct { - At int + At int64 LineNumber int ColumnNumber int Message string @@ -56,73 +56,80 @@ func TestJSON5Decode(t *testing.T) { } t.Logf("file: %s", path) + switch filepath.Ext(path) { case ".json": jd, err := parseJSON() if err != nil { - t.Errorf("unexpected error from json decoder: %s", err) + t.Errorf("%s\nunexpected error from json decoder: %s", path, err) return nil } j5d, err := parseJSON5() if err != nil { - t.Errorf("unexpected error from json5 decoder: %s", err) + t.Errorf("%s\nunexpected error from json5 decoder: %s", path, err) return nil } if diff := pretty.Compare(jd, j5d); diff != "" { - t.Errorf("data is not equal\n%s", diff) + t.Errorf("%s\ndata is not equal\n%s", path, diff) return nil } case ".json5": if _, err := parseJSON(); err == nil { - t.Errorf("expected JSON parsing to fail") + t.Errorf("%s\nexpected JSON parsing to fail", path) return nil } es5d, err := parseES5() if err != nil { - t.Errorf("unexpected error from ES5 decoder: %s", err) + t.Errorf("%s\nunexpected error from ES5 decoder: %s", path, err) return nil } j5d, err := parseJSON5() if err != nil { - t.Errorf("unexpected error from json5 decoder: %s", err) + t.Errorf("%s\nunexpected error from json5 decoder: %s", path, err) return nil } if diff := pretty.Compare(j5d, es5d); diff != "" { - t.Errorf("data is not equal\n%s", diff) + t.Errorf("%s\ndata is not equal\n%s", path, diff) return nil } case ".js": if _, err := parseJSON(); err == nil { - t.Errorf("expected JSON parsing to fail") + t.Errorf("%s\nexpected JSON parsing to fail", path) return nil } if _, err := parseES5(); err != nil { - t.Errorf("unexected error from ES5 decoder: %s", err) + t.Errorf("%s\nunexected error from ES5 decoder: %s", path, err) return nil } if _, err := parseJSON5(); err == nil { - t.Errorf("expected JSON5 parsing to fail") + t.Errorf("%s\nexpected JSON5 parsing to fail", path) return nil } case ".txt": var expectedErr *ErrorSpec - specName := strings.TrimRight(path, filepath.Ext(path)) + ".errorSpec" + specName := path[:len(path)-4] + ".errorSpec" specFile, err := os.Open(specName) if err != nil && !os.IsNotExist(err) { - t.Errorf("error trying to open errorSpec file %s: %s", specName, err) + t.Errorf("%s\nerror trying to open errorSpec file %s: %s", path, specName, err) return nil } if specFile != nil { - defer specFile.Close() expectedErr = &ErrorSpec{} if err := NewDecoder(specFile).Decode(expectedErr); err != nil { - t.Errorf("error decoding %s: %s", specName, err) + specFile.Close() + t.Errorf("%s\nerror decoding %s: %s", path, specName, err) return nil } + specFile.Close() // not using defer because we're in a long loop } _, err = parseJSON5() if err == nil { - t.Errorf("expected JSON5 parsing to fail") + t.Errorf("%s\nexpected JSON5 parsing to fail", path) + return nil + } + //if expectedErr != nil && !matchedError(err, expectedErr) { + if !matchedError(err, expectedErr) { + t.Errorf("%s\nexpected JSON5 error %+v\nbut got: %+v", path, expectedErr, err) return nil } } @@ -131,6 +138,18 @@ func TestJSON5Decode(t *testing.T) { }) } +func matchedError(err error, expected *ErrorSpec) bool { + var se *SyntaxError + if errors.As(err, &se) { + return se.msg == expected.Message && se.Offset == expected.At + } + var ute *UnmarshalTypeError + if errors.As(err, &ute) { + return ute.message() == expected.Message && ute.Offset == expected.At + } + return false +} + // The tests below this comment were found with go-fuzz func TestQuotedQuote(t *testing.T) { @@ -146,9 +165,9 @@ func TestQuotedQuote(t *testing.T) { } func TestInvalidNewline(t *testing.T) { - expected := "invalid character '\\n' in string literal" + expected := "json: invalid character '\\n' in string literal at offset 8" var v interface{} if err := Unmarshal([]byte("{a:'\\\r0\n'}"), &v); err == nil || err.Error() != expected { - t.Errorf("expected error %q, got %s", expected, err) + t.Errorf("expected error %q, got %q", expected, err) } } diff --git a/scanner.go b/scanner.go index 283459a..b3cae1e 100644 --- a/scanner.go +++ b/scanner.go @@ -13,7 +13,10 @@ package json5 // This file starts with two simple examples using the scanner // before diving into the scanner itself. -import "strconv" +import ( + "fmt" + "strconv" +) // checkValid verifies that data is valid JSON-encoded data. // scan is passed in for use by checkValid to avoid an allocation. @@ -66,7 +69,7 @@ type SyntaxError struct { Offset int64 // error occurred after reading Offset bytes } -func (e *SyntaxError) Error() string { return e.msg } +func (e *SyntaxError) Error() string { return fmt.Sprintf("json: %s at offset %d", e.msg, e.Offset) } // A scanner is a JSON scanning state machine. // Callers call scan.reset() and then pass bytes in one at a time @@ -365,7 +368,7 @@ func stateInKeyLiteral(s *scanner, c byte) int { return stateEndValue(s, c) } if !isValidKeyLiteralByte(c) { - return s.error(c, "in key literal") + return s.error(c, "in key literal when expecting ':' or whitespace") } return scanContinue } @@ -426,7 +429,7 @@ func stateEndValue(s *scanner, c byte) int { s.popParseState() return scanEndArray } - return s.error(c, "after array element") + return s.error(c, "after array element (expecting comma or ']')") } return s.error(c, "") } diff --git a/testdata/arrays/no-comma-array.errorSpec b/testdata/arrays/no-comma-array.errorSpec index b476eca..20f288c 100644 --- a/testdata/arrays/no-comma-array.errorSpec +++ b/testdata/arrays/no-comma-array.errorSpec @@ -2,5 +2,5 @@ at: 16, lineNumber: 3, columnNumber: 5, - message: "Expected ']' instead of 'f'" + message: "invalid character 'f' after array element (expecting comma or ']')" } \ No newline at end of file diff --git a/testdata/comments/top-level-block-comment.errorSpec b/testdata/comments/top-level-block-comment.errorSpec index 9bf5cf5..841ab68 100644 --- a/testdata/comments/top-level-block-comment.errorSpec +++ b/testdata/comments/top-level-block-comment.errorSpec @@ -1,6 +1,6 @@ { - at: 77, + at: 76, lineNumber: 4, columnNumber: 3, - message: "Unexpected EOF" + message: "unexpected end of JSON input" } \ No newline at end of file diff --git a/testdata/comments/top-level-inline-comment.errorSpec b/testdata/comments/top-level-inline-comment.errorSpec index 3d915cd..c65945c 100644 --- a/testdata/comments/top-level-inline-comment.errorSpec +++ b/testdata/comments/top-level-inline-comment.errorSpec @@ -1,6 +1,6 @@ { - at: 66, + at: 65, lineNumber: 1, columnNumber: 67, - message: "Unexpected EOF" + message: "unexpected end of JSON input" } \ No newline at end of file diff --git a/testdata/misc/empty.errorSpec b/testdata/misc/empty.errorSpec new file mode 100644 index 0000000..d497733 --- /dev/null +++ b/testdata/misc/empty.errorSpec @@ -0,0 +1,4 @@ +{ + at: 0, + message: "unexpected end of JSON input" +} \ No newline at end of file diff --git a/testdata/numbers/hexadecimal-empty.errorSpec b/testdata/numbers/hexadecimal-empty.errorSpec new file mode 100644 index 0000000..bf6ed3f --- /dev/null +++ b/testdata/numbers/hexadecimal-empty.errorSpec @@ -0,0 +1,4 @@ +{ + at: 3, + message: "invalid character '\\n' in hex number" +} diff --git a/testdata/numbers/integer-with-float-exponent.errorSpec b/testdata/numbers/integer-with-float-exponent.errorSpec new file mode 100644 index 0000000..e9aa299 --- /dev/null +++ b/testdata/numbers/integer-with-float-exponent.errorSpec @@ -0,0 +1,4 @@ +{ + at: 4, + message: "invalid character '.' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/integer-with-hexadecimal-exponent.errorSpec b/testdata/numbers/integer-with-hexadecimal-exponent.errorSpec new file mode 100644 index 0000000..2f7c9cb --- /dev/null +++ b/testdata/numbers/integer-with-hexadecimal-exponent.errorSpec @@ -0,0 +1,4 @@ +{ + at: 4, + message: "invalid character 'x' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/integer-with-negative-float-exponent.errorSpec b/testdata/numbers/integer-with-negative-float-exponent.errorSpec new file mode 100644 index 0000000..b20f973 --- /dev/null +++ b/testdata/numbers/integer-with-negative-float-exponent.errorSpec @@ -0,0 +1,4 @@ +{ + at: 5, + message: "invalid character '.' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/integer-with-negative-hexadecimal-exponent.errorSpec b/testdata/numbers/integer-with-negative-hexadecimal-exponent.errorSpec new file mode 100644 index 0000000..16ec162 --- /dev/null +++ b/testdata/numbers/integer-with-negative-hexadecimal-exponent.errorSpec @@ -0,0 +1,4 @@ +{ + at: 5, + message: "invalid character 'x' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/integer-with-positive-float-exponent.errorSpec b/testdata/numbers/integer-with-positive-float-exponent.errorSpec new file mode 100644 index 0000000..b20f973 --- /dev/null +++ b/testdata/numbers/integer-with-positive-float-exponent.errorSpec @@ -0,0 +1,4 @@ +{ + at: 5, + message: "invalid character '.' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/integer-with-positive-hexadecimal-exponent.errorSpec b/testdata/numbers/integer-with-positive-hexadecimal-exponent.errorSpec new file mode 100644 index 0000000..16ec162 --- /dev/null +++ b/testdata/numbers/integer-with-positive-hexadecimal-exponent.errorSpec @@ -0,0 +1,4 @@ +{ + at: 5, + message: "invalid character 'x' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/lone-decimal-point.errorSpec b/testdata/numbers/lone-decimal-point.errorSpec new file mode 100644 index 0000000..2b94833 --- /dev/null +++ b/testdata/numbers/lone-decimal-point.errorSpec @@ -0,0 +1,4 @@ +{ + at: 1, + message: "cannot unmarshal number . into Go value of type float64" +} diff --git a/testdata/numbers/negative-octal.errorSpec b/testdata/numbers/negative-octal.errorSpec new file mode 100644 index 0000000..27a7be4 --- /dev/null +++ b/testdata/numbers/negative-octal.errorSpec @@ -0,0 +1,4 @@ +{ + at: 3, + message: "invalid character '1' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/negative-zero-octal.errorSpec b/testdata/numbers/negative-zero-octal.errorSpec new file mode 100644 index 0000000..7ac84d6 --- /dev/null +++ b/testdata/numbers/negative-zero-octal.errorSpec @@ -0,0 +1,4 @@ +{ + at: 3, + message: "invalid character '0' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/octal.errorSpec b/testdata/numbers/octal.errorSpec new file mode 100644 index 0000000..ad4cdfb --- /dev/null +++ b/testdata/numbers/octal.errorSpec @@ -0,0 +1,4 @@ +{ + at: 2, + message: "invalid character '1' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/positive-octal.errorSpec b/testdata/numbers/positive-octal.errorSpec new file mode 100644 index 0000000..27a7be4 --- /dev/null +++ b/testdata/numbers/positive-octal.errorSpec @@ -0,0 +1,4 @@ +{ + at: 3, + message: "invalid character '1' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/positive-zero-octal.errorSpec b/testdata/numbers/positive-zero-octal.errorSpec new file mode 100644 index 0000000..7ac84d6 --- /dev/null +++ b/testdata/numbers/positive-zero-octal.errorSpec @@ -0,0 +1,4 @@ +{ + at: 3, + message: "invalid character '0' after top-level value" +} \ No newline at end of file diff --git a/testdata/numbers/zero-octal.errorSpec b/testdata/numbers/zero-octal.errorSpec new file mode 100644 index 0000000..19048bf --- /dev/null +++ b/testdata/numbers/zero-octal.errorSpec @@ -0,0 +1,4 @@ +{ + at: 2, + message: "invalid character '0' after top-level value" +} \ No newline at end of file diff --git a/testdata/objects/illegal-unquoted-key-number.errorSpec b/testdata/objects/illegal-unquoted-key-number.errorSpec index e44dc85..ca679ca 100644 --- a/testdata/objects/illegal-unquoted-key-number.errorSpec +++ b/testdata/objects/illegal-unquoted-key-number.errorSpec @@ -2,5 +2,5 @@ at: 7, lineNumber: 2, columnNumber: 5, - message: "Bad identifier as unquoted key" + message: "invalid character '1' looking for beginning of object key" } \ No newline at end of file diff --git a/testdata/objects/illegal-unquoted-key-symbol.errorSpec b/testdata/objects/illegal-unquoted-key-symbol.errorSpec index 95ba468..f1ceabd 100644 --- a/testdata/objects/illegal-unquoted-key-symbol.errorSpec +++ b/testdata/objects/illegal-unquoted-key-symbol.errorSpec @@ -2,5 +2,5 @@ at: 12, lineNumber: 2, columnNumber: 10, - message: "Expected ':' instead of '-'" + message: "invalid character '-' in key literal when expecting ':' or whitespace" } \ No newline at end of file diff --git a/testdata/objects/leading-comma-object.errorSpec b/testdata/objects/leading-comma-object.errorSpec index e44dc85..3176bcf 100644 --- a/testdata/objects/leading-comma-object.errorSpec +++ b/testdata/objects/leading-comma-object.errorSpec @@ -2,5 +2,5 @@ at: 7, lineNumber: 2, columnNumber: 5, - message: "Bad identifier as unquoted key" + message: "invalid character ',' looking for beginning of object key" } \ No newline at end of file diff --git a/testdata/objects/lone-trailing-comma-object.errorSpec b/testdata/objects/lone-trailing-comma-object.errorSpec new file mode 100644 index 0000000..ee0cb21 --- /dev/null +++ b/testdata/objects/lone-trailing-comma-object.errorSpec @@ -0,0 +1,4 @@ +{ + at: 7, + message: "invalid character ',' looking for beginning of object key" +} \ No newline at end of file diff --git a/testdata/objects/no-comma-object.errorSpec b/testdata/objects/no-comma-object.errorSpec new file mode 100644 index 0000000..2aaf2a3 --- /dev/null +++ b/testdata/objects/no-comma-object.errorSpec @@ -0,0 +1,4 @@ +{ + at: 24, + message: "invalid character '\"' after object key:value pair" +} \ No newline at end of file diff --git a/testdata/strings/unescaped-multi-line-string.errorSpec b/testdata/strings/unescaped-multi-line-string.errorSpec index a85f1ad..e010004 100644 --- a/testdata/strings/unescaped-multi-line-string.errorSpec +++ b/testdata/strings/unescaped-multi-line-string.errorSpec @@ -2,5 +2,5 @@ at: 5, lineNumber: 2, columnNumber: 0, - message: "Bad string" + message: "invalid character '\\n' in string literal" } \ No newline at end of file