diff --git a/Makefile b/Makefile index 0ebe32f..8e720a0 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ generate: root build .root/bin/easyjson -all .root/src/$(PKG)/tests/data.go .root/bin/easyjson -all .root/src/$(PKG)/tests/nothing.go + .root/bin/easyjson -all .root/src/$(PKG)/tests/errors.go .root/bin/easyjson -snake_case .root/src/$(PKG)/tests/snake.go .root/bin/easyjson -omit_empty .root/src/$(PKG)/tests/omitempty.go .root/bin/easyjson -build_tags=use_easyjson .root/src/$(PKG)/benchmark/data.go diff --git a/gen/decoder.go b/gen/decoder.go index bafa162..8a7afbd 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -212,8 +212,13 @@ func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags field if t.NumMethod() != 0 { return fmt.Errorf("interface type %v not supported: only interface{} is allowed", t) } - fmt.Fprintln(g.out, ws+out+" = in.Interface()") - + fmt.Fprintln(g.out, ws+"if m, ok := "+out+".(easyjson.Unmarshaler); ok {") + fmt.Fprintln(g.out, ws+"m.UnmarshalEasyJSON(in)") + fmt.Fprintln(g.out, ws+"} else if m, ok := "+out+".(json.Unmarshaler); ok {") + fmt.Fprintln(g.out, ws+"m.UnmarshalJSON(in.Raw())") + fmt.Fprintln(g.out, ws+"} else {") + fmt.Fprintln(g.out, ws+" "+out+" = in.Interface()") + fmt.Fprintln(g.out, ws+"}") default: return fmt.Errorf("don't know how to decode %v", t) } diff --git a/gen/generator.go b/gen/generator.go index 1b9284d..db232d8 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -15,7 +15,7 @@ import ( const pkgWriter = "github.com/mailru/easyjson/jwriter" const pkgLexer = "github.com/mailru/easyjson/jlexer" -const pkgEasyjson = "github.com/mailru/easyjson" +const pkgEasyJSON = "github.com/mailru/easyjson" // FieldNamer defines a policy for generating names for struct fields. type FieldNamer interface { @@ -60,7 +60,7 @@ func NewGenerator(filename string) *Generator { imports: map[string]string{ pkgWriter: "jwriter", pkgLexer: "jlexer", - pkgEasyjson: "easyjson", + pkgEasyJSON: "easyjson", "encoding/json": "json", }, fieldNamer: DefaultFieldNamer{}, diff --git a/jlexer/lexer.go b/jlexer/lexer.go index eac6cf5..fc0af92 100644 --- a/jlexer/lexer.go +++ b/jlexer/lexer.go @@ -6,10 +6,13 @@ package jlexer import ( "encoding/base64" + "errors" "fmt" "io" "reflect" "strconv" + "unicode" + "unicode/utf16" "unicode/utf8" "unsafe" ) @@ -46,11 +49,13 @@ type Lexer struct { firstElement bool // Whether current element is the first in array or an object. wantSep byte // A comma or a colon character, which need to occur before a token. - err error // Error encountered during lexing, if any. + UseMultipleErrors bool // If we want to use multiple errors. + fatalError error // Fatal error occured during lexing. It is usually a syntax error. + multipleErrors []*LexerError // Semantic errors occured during lexing. Marshalling will be continued after finding this errors. } -// fetchToken scans the input for the next token. -func (r *Lexer) fetchToken() { +// FetchToken scans the input for the next token. +func (r *Lexer) FetchToken() { r.token.kind = tokenUndef r.start = r.pos @@ -148,7 +153,7 @@ func (r *Lexer) fetchToken() { return } } - r.err = io.EOF + r.fatalError = io.EOF return } @@ -266,6 +271,33 @@ func findStringLen(data []byte) (hasEscapes bool, length int) { return false, len(data) } +// getu4 decodes \uXXXX from the beginning of s, returning the hex value, +// or it returns -1. +func getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + var val rune + for i := 2; i < len(s) && i < 6; i++ { + var v byte + c := s[i] + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + v = c - '0' + case 'a', 'b', 'c', 'd', 'e', 'f': + v = c - 'a' + 10 + case 'A', 'B', 'C', 'D', 'E', 'F': + v = c - 'A' + 10 + default: + return -1 + } + + val <<= 4 + val |= rune(v) + } + return val +} + // processEscape processes a single escape sequence and returns number of bytes processed. func (r *Lexer) processEscape(data []byte) (int, error) { if len(data) < 2 { @@ -293,39 +325,28 @@ func (r *Lexer) processEscape(data []byte) (int, error) { r.token.byteValue = append(r.token.byteValue, '\t') return 2, nil case 'u': - default: - return 0, fmt.Errorf("syntax error") - } - - var val rune - - for i := 2; i < len(data) && i < 6; i++ { - var v byte - c = data[i] - switch c { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - v = c - '0' - case 'a', 'b', 'c', 'd', 'e', 'f': - v = c - 'a' + 10 - case 'A', 'B', 'C', 'D', 'E', 'F': - v = c - 'A' + 10 - default: - return 0, fmt.Errorf("syntax error") + rr := getu4(data) + if rr < 0 { + return 0, errors.New("syntax error") } - val <<= 4 - val |= rune(v) + read := 6 + if utf16.IsSurrogate(rr) { + rr1 := getu4(data[read:]) + if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + read += 6 + rr = dec + } else { + rr = unicode.ReplacementChar + } + } + var d [4]byte + s := utf8.EncodeRune(d[:], rr) + r.token.byteValue = append(r.token.byteValue, d[:s]...) + return read, nil } - l := utf8.RuneLen(val) - if l == -1 { - return 0, fmt.Errorf("invalid unicode escape") - } - - var d [4]byte - utf8.EncodeRune(d[:], val) - r.token.byteValue = append(r.token.byteValue, d[:l]...) - return 6, nil + return 0, errors.New("syntax error") } // fetchString scans a string literal token. @@ -369,11 +390,11 @@ func (r *Lexer) fetchString() { // scanToken scans the next token if no token is currently available in the lexer. func (r *Lexer) scanToken() { - if r.token.kind != tokenUndef || r.err != nil { + if r.token.kind != tokenUndef || r.fatalError != nil { return } - r.fetchToken() + r.FetchToken() } // consume resets the current token to allow scanning the next one. @@ -384,20 +405,20 @@ func (r *Lexer) consume() { // Ok returns true if no error (including io.EOF) was encountered during scanning. func (r *Lexer) Ok() bool { - return r.err == nil + return r.fatalError == nil } const maxErrorContextLen = 13 func (r *Lexer) errParse(what string) { - if r.err == nil { + if r.fatalError == nil { var str string if len(r.Data)-r.pos <= maxErrorContextLen { str = string(r.Data) } else { str = string(r.Data[r.pos:r.pos+maxErrorContextLen-3]) + "..." } - r.err = &LexerError{ + r.fatalError = &LexerError{ Reason: what, Offset: r.pos, Data: str, @@ -410,36 +431,64 @@ func (r *Lexer) errSyntax() { } func (r *Lexer) errInvalidToken(expected string) { - if r.err == nil { - var str string - if len(r.token.byteValue) <= maxErrorContextLen { - str = string(r.token.byteValue) - } else { - str = string(r.token.byteValue[:maxErrorContextLen-3]) + "..." - } - r.err = &LexerError{ - Reason: fmt.Sprintf("expected %s", expected), - Offset: r.pos, - Data: str, - } + if r.fatalError != nil { + return } + if r.UseMultipleErrors { + r.pos = r.start + r.consume() + r.SkipRecursive() + switch expected { + case "[": + r.token.delimValue = ']' + r.token.kind = tokenDelim + case "{": + r.token.delimValue = '}' + r.token.kind = tokenDelim + } + r.addNonfatalError(&LexerError{ + Reason: fmt.Sprintf("expected %s", expected), + Offset: r.start, + Data: string(r.Data[r.start:r.pos]), + }) + return + } + + var str string + if len(r.token.byteValue) <= maxErrorContextLen { + str = string(r.token.byteValue) + } else { + str = string(r.token.byteValue[:maxErrorContextLen-3]) + "..." + } + r.fatalError = &LexerError{ + Reason: fmt.Sprintf("expected %s", expected), + Offset: r.pos, + Data: str, + } +} + +func (r *Lexer) GetPos() int { + return r.pos } // Delim consumes a token and verifies that it is the given delimiter. func (r *Lexer) Delim(c byte) { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } + if !r.Ok() || r.token.delimValue != c { + r.consume() // errInvalidToken can change token if UseMultipleErrors is enabled. r.errInvalidToken(string([]byte{c})) + } else { + r.consume() } - r.consume() } // IsDelim returns true if there was no scanning error and next token is the given delimiter. func (r *Lexer) IsDelim(c byte) bool { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } return !r.Ok() || r.token.delimValue == c } @@ -447,7 +496,7 @@ func (r *Lexer) IsDelim(c byte) bool { // Null verifies that the next token is null and consumes it. func (r *Lexer) Null() { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } if !r.Ok() || r.token.kind != tokenNull { r.errInvalidToken("null") @@ -458,7 +507,7 @@ func (r *Lexer) Null() { // IsNull returns true if the next token is a null keyword. func (r *Lexer) IsNull() bool { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } return r.Ok() && r.token.kind == tokenNull } @@ -466,7 +515,7 @@ func (r *Lexer) IsNull() bool { // Skip skips a single token. func (r *Lexer) Skip() { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } r.consume() } @@ -477,7 +526,6 @@ func (r *Lexer) Skip() { // Note: no syntax validation is performed on the skipped data. func (r *Lexer) SkipRecursive() { r.scanToken() - var start, end byte if r.token.delimValue == '{' { @@ -516,7 +564,7 @@ func (r *Lexer) SkipRecursive() { wasEscape = false } r.pos = len(r.Data) - r.err = &LexerError{ + r.fatalError = &LexerError{ Reason: "EOF reached while skipping array/object or token", Offset: r.pos, Data: string(r.Data[r.pos:]), @@ -547,7 +595,7 @@ func (r *Lexer) Consumed() { for _, c := range r.Data[r.pos:] { if c != ' ' && c != '\t' && c != '\r' && c != '\n' { - r.err = &LexerError{ + r.fatalError = &LexerError{ Reason: "invalid character '" + string(c) + "' after top-level value", Offset: r.pos, Data: string(r.Data[r.pos:]), @@ -560,33 +608,37 @@ func (r *Lexer) Consumed() { } } +func (r *Lexer) unsafeString() (string, []byte) { + if r.token.kind == tokenUndef && r.Ok() { + r.FetchToken() + } + if !r.Ok() || r.token.kind != tokenString { + r.errInvalidToken("string") + return "", nil + } + bytes := r.token.byteValue + ret := bytesToStr(r.token.byteValue) + r.consume() + return ret, bytes +} + // UnsafeString returns the string value if the token is a string literal. // // Warning: returned string may point to the input buffer, so the string should not outlive // the input buffer. Intended pattern of usage is as an argument to a switch statement. func (r *Lexer) UnsafeString() string { - if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() - } - if !r.Ok() || r.token.kind != tokenString { - r.errInvalidToken("string") - return "" - } - - ret := bytesToStr(r.token.byteValue) - r.consume() + ret, _ := r.unsafeString() return ret } // String reads a string literal. func (r *Lexer) String() string { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } if !r.Ok() || r.token.kind != tokenString { r.errInvalidToken("string") return "" - } ret := string(r.token.byteValue) r.consume() @@ -596,7 +648,7 @@ func (r *Lexer) String() string { // Bytes reads a string literal and base64 decodes it into a byte slice. func (r *Lexer) Bytes() []byte { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } if !r.Ok() || r.token.kind != tokenString { r.errInvalidToken("string") @@ -605,7 +657,7 @@ func (r *Lexer) Bytes() []byte { ret := make([]byte, base64.StdEncoding.DecodedLen(len(r.token.byteValue))) len, err := base64.StdEncoding.Decode(ret, r.token.byteValue) if err != nil { - r.err = &LexerError{ + r.fatalError = &LexerError{ Reason: err.Error(), } return nil @@ -618,12 +670,11 @@ func (r *Lexer) Bytes() []byte { // Bool reads a true or false boolean keyword. func (r *Lexer) Bool() bool { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } if !r.Ok() || r.token.kind != tokenBool { r.errInvalidToken("bool") return false - } ret := r.token.boolValue r.consume() @@ -632,12 +683,11 @@ func (r *Lexer) Bool() bool { func (r *Lexer) number() string { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } if !r.Ok() || r.token.kind != tokenNumber { r.errInvalidToken("number") return "" - } ret := bytesToStr(r.token.byteValue) r.consume() @@ -652,9 +702,11 @@ func (r *Lexer) Uint8() uint8 { n, err := strconv.ParseUint(s, 10, 8) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return uint8(n) } @@ -667,9 +719,11 @@ func (r *Lexer) Uint16() uint16 { n, err := strconv.ParseUint(s, 10, 16) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return uint16(n) } @@ -682,9 +736,11 @@ func (r *Lexer) Uint32() uint32 { n, err := strconv.ParseUint(s, 10, 32) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return uint32(n) } @@ -697,9 +753,11 @@ func (r *Lexer) Uint64() uint64 { n, err := strconv.ParseUint(s, 10, 64) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return n } @@ -716,9 +774,11 @@ func (r *Lexer) Int8() int8 { n, err := strconv.ParseInt(s, 10, 8) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return int8(n) } @@ -731,9 +791,11 @@ func (r *Lexer) Int16() int16 { n, err := strconv.ParseInt(s, 10, 16) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return int16(n) } @@ -746,9 +808,11 @@ func (r *Lexer) Int32() int32 { n, err := strconv.ParseInt(s, 10, 32) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return int32(n) } @@ -761,9 +825,11 @@ func (r *Lexer) Int64() int64 { n, err := strconv.ParseInt(s, 10, 64) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return n } @@ -773,61 +839,69 @@ func (r *Lexer) Int() int { } func (r *Lexer) Uint8Str() uint8 { - s := r.UnsafeString() + s, b := r.unsafeString() if !r.Ok() { return 0 } n, err := strconv.ParseUint(s, 10, 8) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: string(b), + }) } return uint8(n) } func (r *Lexer) Uint16Str() uint16 { - s := r.UnsafeString() + s, b := r.unsafeString() if !r.Ok() { return 0 } n, err := strconv.ParseUint(s, 10, 16) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: string(b), + }) } return uint16(n) } func (r *Lexer) Uint32Str() uint32 { - s := r.UnsafeString() + s, b := r.unsafeString() if !r.Ok() { return 0 } n, err := strconv.ParseUint(s, 10, 32) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: string(b), + }) } return uint32(n) } func (r *Lexer) Uint64Str() uint64 { - s := r.UnsafeString() + s, b := r.unsafeString() if !r.Ok() { return 0 } n, err := strconv.ParseUint(s, 10, 64) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: string(b), + }) } return n } @@ -837,61 +911,69 @@ func (r *Lexer) UintStr() uint { } func (r *Lexer) Int8Str() int8 { - s := r.UnsafeString() + s, b := r.unsafeString() if !r.Ok() { return 0 } n, err := strconv.ParseInt(s, 10, 8) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: string(b), + }) } return int8(n) } func (r *Lexer) Int16Str() int16 { - s := r.UnsafeString() + s, b := r.unsafeString() if !r.Ok() { return 0 } n, err := strconv.ParseInt(s, 10, 16) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: string(b), + }) } return int16(n) } func (r *Lexer) Int32Str() int32 { - s := r.UnsafeString() + s, b := r.unsafeString() if !r.Ok() { return 0 } n, err := strconv.ParseInt(s, 10, 32) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: string(b), + }) } return int32(n) } func (r *Lexer) Int64Str() int64 { - s := r.UnsafeString() + s, b := r.unsafeString() if !r.Ok() { return 0 } n, err := strconv.ParseInt(s, 10, 64) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: string(b), + }) } return n } @@ -908,9 +990,11 @@ func (r *Lexer) Float32() float32 { n, err := strconv.ParseFloat(s, 32) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return float32(n) } @@ -923,27 +1007,45 @@ func (r *Lexer) Float64() float64 { n, err := strconv.ParseFloat(s, 64) if err != nil { - r.err = &LexerError{ + r.addNonfatalError(&LexerError{ + Offset: r.start, Reason: err.Error(), - } + Data: s, + }) } return n } func (r *Lexer) Error() error { - return r.err + return r.fatalError } func (r *Lexer) AddError(e error) { - if r.err == nil { - r.err = e + if r.fatalError == nil { + r.fatalError = e } } +func (r *Lexer) addNonfatalError(err *LexerError) { + if r.UseMultipleErrors { + // We don't want to add errors with the same offset. + if len(r.multipleErrors) != 0 && r.multipleErrors[len(r.multipleErrors)-1].Offset == err.Offset { + return + } + r.multipleErrors = append(r.multipleErrors, err) + return + } + r.fatalError = err +} + +func (r *Lexer) GetNonFatalErrors() []*LexerError { + return r.multipleErrors +} + // Interface fetches an interface{} analogous to the 'encoding/json' package. func (r *Lexer) Interface() interface{} { if r.token.kind == tokenUndef && r.Ok() { - r.fetchToken() + r.FetchToken() } if !r.Ok() { diff --git a/jlexer/lexer_test.go b/jlexer/lexer_test.go index e3add8c..b8a6498 100644 --- a/jlexer/lexer_test.go +++ b/jlexer/lexer_test.go @@ -18,12 +18,15 @@ func TestString(t *testing.T) { {toParse: `"\u0020"`, want: " "}, {toParse: `"\u0020-\t"`, want: " -\t"}, {toParse: `"\ufffd\uFFFD"`, want: "\ufffd\ufffd"}, + {toParse: `"\ud83d\ude00"`, want: "😀"}, + {toParse: `"\ud83d\ude08"`, want: "😈"}, + {toParse: `"\ud8"`, wantError: true}, {toParse: `"test"junk`, want: "test"}, {toParse: `5`, wantError: true}, // not a string {toParse: `"\x"`, wantError: true}, // invalid escape - {toParse: `"\ud800"`, wantError: true}, // invalid utf-8 char + {toParse: `"\ud800"`, want: "�"}, // invalid utf-8 char; return replacement char } { l := Lexer{Data: []byte(test.toParse)} diff --git a/jwriter/writer.go b/jwriter/writer.go index a3ef534..04bfddf 100644 --- a/jwriter/writer.go +++ b/jwriter/writer.go @@ -23,8 +23,9 @@ const ( type Writer struct { Flags Flags - Error error - Buffer buffer.Buffer + Error error + Buffer buffer.Buffer + NoEscapeHTML bool } // Size returns the size of the data that was written out. @@ -225,10 +226,14 @@ func (w *Writer) Bool(v bool) { const chars = "0123456789abcdef" -func isNotEscapedSingleChar(c byte) bool { +func isNotEscapedSingleChar(c byte, escapeHTML bool) bool { // Note: might make sense to use a table if there are more chars to escape. With 4 chars // it benchmarks the same. - return c != '<' && c != '\\' && c != '"' && c != '>' && c >= 0x20 && c < utf8.RuneSelf + if escapeHTML { + return c != '<' && c != '>' && c != '&' && c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf + } else { + return c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf + } } func (w *Writer) String(s string) { @@ -242,7 +247,7 @@ func (w *Writer) String(s string) { for i := 0; i < len(s); { c := s[i] - if isNotEscapedSingleChar(c) { + if isNotEscapedSingleChar(c, !w.NoEscapeHTML) { // single-width character, no escaping is required i++ continue diff --git a/tests/basic_test.go b/tests/basic_test.go index c072f28..4e7c20c 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -207,3 +207,15 @@ func TestNestedEasyJsonMarshal(t *testing.T) { } } } + +func TestUnmarshalStructWithEmbeddedPtrStruct(t *testing.T) { + var s = StructWithInterface{Field2: &EmbeddedStruct{}} + var err error + err = easyjson.Unmarshal([]byte(structWithInterfaceString), &s) + if err != nil { + t.Errorf("easyjson.Unmarshal() error: %v", err) + } + if !reflect.DeepEqual(s, structWithInterfaceValueFilled) { + t.Errorf("easyjson.Unmarshal() = %#v; want %#v", s, structWithInterfaceValueFilled) + } +} diff --git a/tests/data.go b/tests/data.go index a46a4a5..b13bcd8 100644 --- a/tests/data.go +++ b/tests/data.go @@ -634,3 +634,17 @@ type EncodingFlagsTestMap struct { type EncodingFlagsTestSlice struct { F []string } + +type StructWithInterface struct { + Field1 int `json:"f1"` + Field2 interface{} `json:"f2"` + Field3 string `json:"f3"` +} + +type EmbeddedStruct struct { + Field1 int `json:"f1"` + Field2 string `json:"f2"` +} + +var structWithInterfaceString = `{"f1":1,"f2":{"f1":11,"f2":"22"},"f3":"3"}` +var structWithInterfaceValueFilled = StructWithInterface{1, &EmbeddedStruct{11, "22"}, "3"} diff --git a/tests/errors.go b/tests/errors.go new file mode 100644 index 0000000..2ec3299 --- /dev/null +++ b/tests/errors.go @@ -0,0 +1,23 @@ +package tests + +//easyjson:json +type ErrorIntSlice []int + +//easyjson:json +type ErrorBoolSlice []bool + +//easyjson:json +type ErrorUintSlice []uint + +//easyjson:json +type ErrorStruct struct { + Int int `json:"int"` + String string `json:"string"` + Slice []int `json:"slice"` + IntSlice []int `json:"int_slice"` +} + +type ErrorNestedStruct struct { + ErrorStruct ErrorStruct `json:"error_struct"` + Int int `json:"int"` +} diff --git a/tests/errors_test.go b/tests/errors_test.go new file mode 100644 index 0000000..756f7db --- /dev/null +++ b/tests/errors_test.go @@ -0,0 +1,243 @@ +package tests + +import ( + "testing" + + "github.com/mailru/easyjson/jlexer" +) + +func TestMultipleErrorsInt(t *testing.T) { + for i, test := range []struct { + Data []byte + Offsets []int + }{ + { + Data: []byte(`[1, 2, 3, "4", "5"]`), + Offsets: []int{10, 15}, + }, + { + Data: []byte(`[1, {"2":"3"}, 3, "4"]`), + Offsets: []int{4, 18}, + }, + { + Data: []byte(`[1, "2", "3", "4", "5", "6"]`), + Offsets: []int{4, 9, 14, 19, 24}, + }, + { + Data: []byte(`[1, 2, 3, 4, "5"]`), + Offsets: []int{13}, + }, + { + Data: []byte(`[{"1": "2"}]`), + Offsets: []int{1}, + }, + } { + l := jlexer.Lexer{ + Data: test.Data, + UseMultipleErrors: true, + } + + var v ErrorIntSlice + + v.UnmarshalEasyJSON(&l) + + errors := l.GetNonFatalErrors() + + if len(errors) != len(test.Offsets) { + t.Errorf("[%d] TestMultipleErrorsInt(): errornum: want: %d, got %d", i, len(test.Offsets), len(errors)) + return + } + + for ii, e := range errors { + if e.Offset != test.Offsets[ii] { + t.Errorf("[%d] TestMultipleErrorsInt(): offset[%d]: want %d, got %d", i, ii, test.Offsets[ii], e.Offset) + } + } + } +} + +func TestMultipleErrorsBool(t *testing.T) { + for i, test := range []struct { + Data []byte + Offsets []int + }{ + { + Data: []byte(`[true, false, true, false]`), + }, + { + Data: []byte(`["test", "value", "lol", "1"]`), + Offsets: []int{1, 9, 18, 25}, + }, + { + Data: []byte(`[true, 42, {"a":"b", "c":"d"}, false]`), + Offsets: []int{7, 11}, + }, + } { + l := jlexer.Lexer{ + Data: test.Data, + UseMultipleErrors: true, + } + + var v ErrorBoolSlice + v.UnmarshalEasyJSON(&l) + + errors := l.GetNonFatalErrors() + + if len(errors) != len(test.Offsets) { + t.Errorf("[%d] TestMultipleErrorsBool(): errornum: want: %d, got %d", i, len(test.Offsets), len(errors)) + return + } + for ii, e := range errors { + if e.Offset != test.Offsets[ii] { + t.Errorf("[%d] TestMultipleErrorsBool(): offset[%d]: want %d, got %d", i, ii, test.Offsets[ii], e.Offset) + } + } + } +} + +func TestMultipleErrorsUint(t *testing.T) { + for i, test := range []struct { + Data []byte + Offsets []int + }{ + { + Data: []byte(`[42, 42, 42]`), + }, + { + Data: []byte(`[17, "42", 32]`), + Offsets: []int{5}, + }, + { + Data: []byte(`["zz", "zz"]`), + Offsets: []int{1, 7}, + }, + { + Data: []byte(`[{}, 42]`), + Offsets: []int{1}, + }, + } { + l := jlexer.Lexer{ + Data: test.Data, + UseMultipleErrors: true, + } + + var v ErrorUintSlice + v.UnmarshalEasyJSON(&l) + + errors := l.GetNonFatalErrors() + + if len(errors) != len(test.Offsets) { + t.Errorf("[%d] TestMultipleErrorsUint(): errornum: want: %d, got %d", i, len(test.Offsets), len(errors)) + return + } + for ii, e := range errors { + if e.Offset != test.Offsets[ii] { + t.Errorf("[%d] TestMultipleErrorsUint(): offset[%d]: want %d, got %d", i, ii, test.Offsets[ii], e.Offset) + } + } + } +} + +func TestMultipleErrorsStruct(t *testing.T) { + for i, test := range []struct { + Data []byte + Offsets []int + }{ + { + Data: []byte(`{"string": "test", "slice":[42, 42, 42], "int_slice":[1, 2, 3]}`), + }, + { + Data: []byte(`{"string": {"test": "test"}, "slice":[42, 42, 42], "int_slice":["1", 2, 3]}`), + Offsets: []int{11, 64}, + }, + { + Data: []byte(`{"slice": [42, 42], "string": {"test": "test"}, "int_slice":["1", "2", 3]}`), + Offsets: []int{30, 61, 66}, + }, + { + Data: []byte(`{"string": "test", "slice": {}}`), + Offsets: []int{28}, + }, + { + Data: []byte(`{"slice":5, "string" : "test"}`), + Offsets: []int{9}, + }, + { + Data: []byte(`{"slice" : "test", "string" : "test"}`), + Offsets: []int{11}, + }, + { + Data: []byte(`{"slice": "", "string" : {}, "int":{}}`), + Offsets: []int{10, 25, 35}, + }, + } { + l := jlexer.Lexer{ + Data: test.Data, + UseMultipleErrors: true, + } + var v ErrorStruct + v.UnmarshalEasyJSON(&l) + + errors := l.GetNonFatalErrors() + + if len(errors) != len(test.Offsets) { + t.Errorf("[%d] TestMultipleErrorsStruct(): errornum: want: %d, got %d", i, len(test.Offsets), len(errors)) + return + } + for ii, e := range errors { + if e.Offset != test.Offsets[ii] { + t.Errorf("[%d] TestMultipleErrorsStruct(): offset[%d]: want %d, got %d", i, ii, test.Offsets[ii], e.Offset) + } + } + } +} + +func TestMultipleErrorsNestedStruct(t *testing.T) { + for i, test := range []struct { + Data []byte + Offsets []int + }{ + { + Data: []byte(`{"error_struct":{}}`), + }, + { + Data: []byte(`{"error_struct":5}`), + Offsets: []int{16}, + }, + { + Data: []byte(`{"error_struct":[]}`), + Offsets: []int{16}, + }, + { + Data: []byte(`{"error_struct":{"int":{}}}`), + Offsets: []int{23}, + }, + { + Data: []byte(`{"error_struct":{"int_slice":{}}, "int":4}`), + Offsets: []int{29}, + }, + { + Data: []byte(`{"error_struct":{"int_slice":["1", 2, "3"]}, "int":[]}`), + Offsets: []int{30, 38, 51}, + }, + } { + l := jlexer.Lexer{ + Data: test.Data, + UseMultipleErrors: true, + } + var v ErrorNestedStruct + v.UnmarshalEasyJSON(&l) + + errors := l.GetNonFatalErrors() + + if len(errors) != len(test.Offsets) { + t.Errorf("[%d] TestMultipleErrorsNestedStruct(): errornum: want: %d, got %d", i, len(test.Offsets), len(errors)) + return + } + for ii, e := range errors { + if e.Offset != test.Offsets[ii] { + t.Errorf("[%d] TestMultipleErrorsNestedStruct(): offset[%d]: want %d, got %d", i, ii, test.Offsets[ii], e.Offset) + } + } + } +}