Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down
14 changes: 7 additions & 7 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
Expand All @@ -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)
}
}
}
Expand All @@ -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)
}
}
57 changes: 38 additions & 19 deletions json5_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ package json5

import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

"github.com/kylelemons/godebug/pretty"
"github.com/robertkrimen/otto"
)

type ErrorSpec struct {
At int
At int64
LineNumber int
ColumnNumber int
Message string
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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) {
Expand All @@ -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)
}
}
11 changes: 7 additions & 4 deletions scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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, "")
}
Expand Down
2 changes: 1 addition & 1 deletion testdata/arrays/no-comma-array.errorSpec
Original file line number Diff line number Diff line change
Expand Up @@ -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 ']')"
}
4 changes: 2 additions & 2 deletions testdata/comments/top-level-block-comment.errorSpec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
at: 77,
at: 76,
lineNumber: 4,
columnNumber: 3,
message: "Unexpected EOF"
message: "unexpected end of JSON input"
}
4 changes: 2 additions & 2 deletions testdata/comments/top-level-inline-comment.errorSpec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
at: 66,
at: 65,
lineNumber: 1,
columnNumber: 67,
message: "Unexpected EOF"
message: "unexpected end of JSON input"
}
4 changes: 4 additions & 0 deletions testdata/misc/empty.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 0,
message: "unexpected end of JSON input"
}
4 changes: 4 additions & 0 deletions testdata/numbers/hexadecimal-empty.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 3,
message: "invalid character '\\n' in hex number"
}
4 changes: 4 additions & 0 deletions testdata/numbers/integer-with-float-exponent.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 4,
message: "invalid character '.' after top-level value"
}
4 changes: 4 additions & 0 deletions testdata/numbers/integer-with-hexadecimal-exponent.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 4,
message: "invalid character 'x' after top-level value"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 5,
message: "invalid character '.' after top-level value"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 5,
message: "invalid character 'x' after top-level value"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 5,
message: "invalid character '.' after top-level value"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 5,
message: "invalid character 'x' after top-level value"
}
4 changes: 4 additions & 0 deletions testdata/numbers/lone-decimal-point.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 1,
message: "cannot unmarshal number . into Go value of type float64"
}
4 changes: 4 additions & 0 deletions testdata/numbers/negative-octal.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 3,
message: "invalid character '1' after top-level value"
}
4 changes: 4 additions & 0 deletions testdata/numbers/negative-zero-octal.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 3,
message: "invalid character '0' after top-level value"
}
4 changes: 4 additions & 0 deletions testdata/numbers/octal.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 2,
message: "invalid character '1' after top-level value"
}
4 changes: 4 additions & 0 deletions testdata/numbers/positive-octal.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 3,
message: "invalid character '1' after top-level value"
}
4 changes: 4 additions & 0 deletions testdata/numbers/positive-zero-octal.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 3,
message: "invalid character '0' after top-level value"
}
4 changes: 4 additions & 0 deletions testdata/numbers/zero-octal.errorSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
at: 2,
message: "invalid character '0' after top-level value"
}
2 changes: 1 addition & 1 deletion testdata/objects/illegal-unquoted-key-number.errorSpec
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Loading