diff --git a/benchmarks/bench_test.go b/benchmarks/bench_test.go index e199620..d671811 100644 --- a/benchmarks/bench_test.go +++ b/benchmarks/bench_test.go @@ -581,3 +581,63 @@ func BenchmarkUnmapped(b *testing.B) { } }) } + +func Benchmark_Compact_EncodingJson(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := stdjson.Compact(&buf, codeJSON); err != nil { + b.Fatal(err) + } + } +} + +func Benchmark_Compact_GoJson(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := json.Compact(&buf, codeJSON); err != nil { + b.Fatal(err) + } + } +} + +func Benchmark_Indent_EncodingJson(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := stdjson.Indent(&buf, codeJSON, "-", " "); err != nil { + b.Fatal(err) + } + } +} + +func Benchmark_Indent_GoJson(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + for i := 0; i < b.N; i++ { + var buf bytes.Buffer + if err := json.Indent(&buf, codeJSON, "-", " "); err != nil { + b.Fatal(err) + } + } +} diff --git a/internal/encoder/compact.go b/internal/encoder/compact.go index cf3f7fa..4f1bd79 100644 --- a/internal/encoder/compact.go +++ b/internal/encoder/compact.go @@ -2,55 +2,272 @@ package encoder import ( "bytes" + "fmt" + "strconv" + "unsafe" "github.com/goccy/go-json/internal/errors" ) -func Compact(dst *bytes.Buffer, src []byte, escape bool) error { +var ( + isWhiteSpace = [256]bool{ + ' ': true, + '\n': true, + '\t': true, + '\r': true, + } + isHTMLEscapeChar = [256]bool{ + '<': true, + '>': true, + '&': true, + } + nul = byte('\000') +) + +func Compact(buf *bytes.Buffer, src []byte, escape bool) error { if len(src) == 0 { return errors.ErrUnexpectedEndOfJSON("", 0) } - length := len(src) - for cursor := 0; cursor < length; cursor++ { - c := src[cursor] - switch c { - case ' ', '\t', '\n', '\r': - continue - case '"': - if err := dst.WriteByte(c); err != nil { - return err - } - for { - cursor++ - c := src[cursor] - if escape && (c == '<' || c == '>' || c == '&') { - if _, err := dst.WriteString(`\u00`); err != nil { - return err - } - if _, err := dst.Write([]byte{hex[c>>4], hex[c&0xF]}); err != nil { - return err - } - } else if err := dst.WriteByte(c); err != nil { - return err - } - switch c { - case '\\': - cursor++ - if err := dst.WriteByte(src[cursor]); err != nil { - return err - } - case '"': - goto LOOP_END - case '\000': - return errors.ErrUnexpectedEndOfJSON("string", int64(length)) - } - } - default: - if err := dst.WriteByte(c); err != nil { - return err - } - } - LOOP_END: + buf.Grow(len(src)) + dst := buf.Bytes() + newSrc := make([]byte, len(src)+1) // append nul byte to the end + copy(newSrc, src) + dst, err := compact(dst, newSrc, escape) + if err != nil { + return err + } + if _, err := buf.Write(dst); err != nil { + return err } return nil } + +func compact(dst, src []byte, escape bool) ([]byte, error) { + buf, cursor, err := compactValue(dst, src, 0, escape) + if err != nil { + return nil, err + } + if err := validateEndBuf(src, cursor); err != nil { + return nil, err + } + return buf, nil +} + +func validateEndBuf(src []byte, cursor int64) error { + for { + switch src[cursor] { + case ' ', '\t', '\n', '\r': + cursor++ + continue + case nul: + return nil + } + return errors.ErrSyntax( + fmt.Sprintf("invalid character '%c' after top-level value", src[cursor]), + cursor+1, + ) + } +} + +func skipWhiteSpace(buf []byte, cursor int64) int64 { +LOOP: + if isWhiteSpace[buf[cursor]] { + cursor++ + goto LOOP + } + return cursor +} + +func compactValue(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { + for { + switch src[cursor] { + case ' ', '\t', '\n', '\r': + cursor++ + continue + case '{': + return compactObject(dst, src, cursor, escape) + case '}': + return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor) + case '[': + return compactArray(dst, src, cursor, escape) + case ']': + return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor) + case '"': + return compactString(dst, src, cursor, escape) + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return compactNumber(dst, src, cursor) + case 't': + return compactTrue(dst, src, cursor) + case 'f': + return compactFalse(dst, src, cursor) + case 'n': + return compactNull(dst, src, cursor) + default: + return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor) + } + } +} + +func compactObject(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { + if src[cursor] == '{' { + dst = append(dst, '{') + } else { + return nil, 0, errors.ErrExpected("expected { character for object value", cursor) + } + cursor = skipWhiteSpace(src, cursor+1) + if src[cursor] == '}' { + dst = append(dst, '}') + return dst, cursor + 1, nil + } + var err error + for { + cursor = skipWhiteSpace(src, cursor) + dst, cursor, err = compactString(dst, src, cursor, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + if src[cursor] != ':' { + return nil, 0, errors.ErrExpected("colon after object key", cursor) + } + dst = append(dst, ':') + dst, cursor, err = compactValue(dst, src, cursor+1, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + switch src[cursor] { + case '}': + dst = append(dst, '}') + cursor++ + return dst, cursor, nil + case ',': + dst = append(dst, ',') + default: + return nil, 0, errors.ErrExpected("comma after object value", cursor) + } + cursor++ + } +} + +func compactArray(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { + if src[cursor] == '[' { + dst = append(dst, '[') + } else { + return nil, 0, errors.ErrExpected("expected [ character for array value", cursor) + } + cursor = skipWhiteSpace(src, cursor+1) + if src[cursor] == ']' { + dst = append(dst, ']') + return dst, cursor + 1, nil + } + var err error + for { + dst, cursor, err = compactValue(dst, src, cursor, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + switch src[cursor] { + case ']': + dst = append(dst, ']') + cursor++ + return dst, cursor, nil + case ',': + dst = append(dst, ',') + default: + return nil, 0, errors.ErrExpected("comma after array value", cursor) + } + cursor++ + } +} + +func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { + if src[cursor] != '"' { + return nil, 0, errors.ErrInvalidCharacter(src[cursor], "string", cursor) + } + start := cursor + for { + cursor++ + c := src[cursor] + if escape { + if isHTMLEscapeChar[c] { + dst = append(dst, src[start:cursor]...) + dst = append(dst, `\u00`...) + dst = append(dst, hex[c>>4], hex[c&0xF]) + start = cursor + 1 + } else if c == 0xE2 && cursor+2 < int64(len(src)) && src[cursor+1] == 0x80 && src[cursor+2]&^1 == 0xA8 { + dst = append(dst, src[start:cursor]...) + dst = append(dst, `\u202`...) + dst = append(dst, hex[src[cursor+2]&0xF]) + cursor += 2 + start = cursor + 3 + } + } + switch c { + case '\\': + cursor++ + if src[cursor] == nul { + return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src))) + } + case '"': + cursor++ + return append(dst, src[start:cursor]...), cursor, nil + case nul: + return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src))) + } + } +} + +func compactNumber(dst, src []byte, cursor int64) ([]byte, int64, error) { + start := cursor + for { + cursor++ + if floatTable[src[cursor]] { + continue + } + break + } + num := src[start:cursor] + if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&num)), 64); err != nil { + return nil, 0, err + } + dst = append(dst, num...) + return dst, cursor, nil +} + +func compactTrue(dst, src []byte, cursor int64) ([]byte, int64, error) { + if cursor+3 >= int64(len(src)) { + return nil, 0, errors.ErrUnexpectedEndOfJSON("true", cursor) + } + if !bytes.Equal(src[cursor:cursor+4], []byte(`true`)) { + return nil, 0, errors.ErrInvalidCharacter(src[cursor], "true", cursor) + } + dst = append(dst, "true"...) + cursor += 4 + return dst, cursor, nil +} + +func compactFalse(dst, src []byte, cursor int64) ([]byte, int64, error) { + if cursor+4 >= int64(len(src)) { + return nil, 0, errors.ErrUnexpectedEndOfJSON("false", cursor) + } + if !bytes.Equal(src[cursor:cursor+5], []byte(`false`)) { + return nil, 0, errors.ErrInvalidCharacter(src[cursor], "false", cursor) + } + dst = append(dst, "false"...) + cursor += 5 + return dst, cursor, nil +} + +func compactNull(dst, src []byte, cursor int64) ([]byte, int64, error) { + if cursor+3 >= int64(len(src)) { + return nil, 0, errors.ErrUnexpectedEndOfJSON("null", cursor) + } + if !bytes.Equal(src[cursor:cursor+4], []byte(`null`)) { + return nil, 0, errors.ErrInvalidCharacter(src[cursor], "null", cursor) + } + dst = append(dst, "null"...) + cursor += 4 + return dst, cursor, nil +} diff --git a/internal/encoder/encoder.go b/internal/encoder/encoder.go index 5363f52..44adee0 100644 --- a/internal/encoder/encoder.go +++ b/internal/encoder/encoder.go @@ -306,6 +306,7 @@ func MapLen(m unsafe.Pointer) int type RuntimeContext struct { Buf []byte + MarshalBuf []byte Ptrs []uintptr KeepRefs []unsafe.Pointer SeenPtr []uintptr @@ -413,7 +414,7 @@ func AppendNumber(b []byte, n json.Number) ([]byte, error) { return b, nil } -func AppendMarshalJSON(code *Opcode, b []byte, v interface{}, escape bool) ([]byte, error) { +func AppendMarshalJSON(ctx *RuntimeContext, code *Opcode, b []byte, v interface{}, escape bool) ([]byte, error) { rv := reflect.ValueOf(v) // convert by dynamic interface type if code.AddrForMarshaler { if rv.CanAddr() { @@ -433,12 +434,14 @@ func AppendMarshalJSON(code *Opcode, b []byte, v interface{}, escape bool) ([]by if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } - buf := bytes.NewBuffer(b) - // TODO: we should validate buffer with `compact` - if err := Compact(buf, bb, escape); err != nil { + marshalBuf := ctx.MarshalBuf[:0] + marshalBuf = append(append(marshalBuf, bb...), nul) + compactedBuf, err := compact(b, marshalBuf, escape) + if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } - return buf.Bytes(), nil + ctx.MarshalBuf = marshalBuf + return compactedBuf, nil } func AppendMarshalJSONIndent(ctx *RuntimeContext, code *Opcode, b []byte, v interface{}, indent int, escape bool) ([]byte, error) { @@ -461,20 +464,20 @@ func AppendMarshalJSONIndent(ctx *RuntimeContext, code *Opcode, b []byte, v inte if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } - var compactBuf bytes.Buffer - if err := Compact(&compactBuf, bb, escape); err != nil { - return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} - } - var indentBuf bytes.Buffer - if err := Indent( - &indentBuf, - compactBuf.Bytes(), + marshalBuf := ctx.MarshalBuf[:0] + marshalBuf = append(append(marshalBuf, bb...), nul) + indentedBuf, err := doIndent( + b, + marshalBuf, string(ctx.Prefix)+strings.Repeat(string(ctx.IndentStr), ctx.BaseIndent+indent), string(ctx.IndentStr), - ); err != nil { + escape, + ) + if err != nil { return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} } - return append(b, indentBuf.Bytes()...), nil + ctx.MarshalBuf = marshalBuf + return indentedBuf, nil } func AppendMarshalText(code *Opcode, b []byte, v interface{}, escape bool) ([]byte, error) { diff --git a/internal/encoder/indent.go b/internal/encoder/indent.go index fba4519..d593301 100644 --- a/internal/encoder/indent.go +++ b/internal/encoder/indent.go @@ -2,111 +2,176 @@ package encoder import ( "bytes" + "fmt" "github.com/goccy/go-json/internal/errors" ) -func Indent(dst *bytes.Buffer, src []byte, prefix, indentStr string) error { - length := int64(len(src)) - indentNum := 0 - indentBytes := []byte(indentStr) - for cursor := int64(0); cursor < length; cursor++ { - c := src[cursor] - switch c { - case ' ', '\t', '\n', '\r': - continue - case '"': - if err := dst.WriteByte(c); err != nil { - return err - } - for { - cursor++ - if err := dst.WriteByte(src[cursor]); err != nil { - return err - } - switch src[cursor] { - case '\\': - cursor++ - if err := dst.WriteByte(src[cursor]); err != nil { - return err - } - case '"': - goto LOOP_END - case '\000': - return errors.ErrUnexpectedEndOfJSON("string", length) - } - } - case '{': - if cursor+1 < length && src[cursor+1] == '}' { - if _, err := dst.Write([]byte{'{', '}'}); err != nil { - return err - } - cursor++ - } else { - indentNum++ - b := []byte{c, '\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - if _, err := dst.Write(b); err != nil { - return err - } - } - case '}': - indentNum-- - if indentNum < 0 { - return errors.ErrInvalidCharacter('}', "}", cursor) - } - b := []byte{'\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - b = append(b, c) - if _, err := dst.Write(b); err != nil { - return err - } - case '[': - if cursor+1 < length && src[cursor+1] == ']' { - if _, err := dst.Write([]byte{'[', ']'}); err != nil { - return err - } - cursor++ - } else { - indentNum++ - b := []byte{c, '\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - if _, err := dst.Write(b); err != nil { - return err - } - } - case ']': - indentNum-- - if indentNum < 0 { - return errors.ErrInvalidCharacter(']', "]", cursor) - } - b := []byte{'\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - b = append(b, c) - if _, err := dst.Write(b); err != nil { - return err - } - case ':': - if _, err := dst.Write([]byte{':', ' '}); err != nil { - return err - } - case ',': - b := []byte{',', '\n'} - b = append(b, prefix...) - b = append(b, bytes.Repeat(indentBytes, indentNum)...) - if _, err := dst.Write(b); err != nil { - return err - } - default: - if err := dst.WriteByte(c); err != nil { - return err - } - } - LOOP_END: +func Indent(buf *bytes.Buffer, src []byte, prefix, indentStr string) error { + if len(src) == 0 { + return errors.ErrUnexpectedEndOfJSON("", 0) + } + buf.Grow(len(src)) + dst := buf.Bytes() + newSrc := make([]byte, len(src)+1) // append nul byte to the end + copy(newSrc, src) + dst, err := doIndent(dst, newSrc, prefix, indentStr, false) + if err != nil { + return err + } + if _, err := buf.Write(dst); err != nil { + return err } return nil } + +func doIndent(dst, src []byte, prefix, indentStr string, escape bool) ([]byte, error) { + buf, cursor, err := indentValue(dst, src, 0, 0, []byte(prefix), []byte(indentStr), escape) + if err != nil { + return nil, err + } + if err := validateEndBuf(src, cursor); err != nil { + return nil, err + } + return buf, nil +} + +func indentValue( + dst []byte, + src []byte, + indentNum int, + cursor int64, + prefix []byte, + indentBytes []byte, + escape bool) ([]byte, int64, error) { + for { + switch src[cursor] { + case ' ', '\t', '\n', '\r': + cursor++ + continue + case '{': + return indentObject(dst, src, indentNum, cursor, prefix, indentBytes, escape) + case '}': + return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor) + case '[': + return indentArray(dst, src, indentNum, cursor, prefix, indentBytes, escape) + case ']': + return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor) + case '"': + return compactString(dst, src, cursor, escape) + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return compactNumber(dst, src, cursor) + case 't': + return compactTrue(dst, src, cursor) + case 'f': + return compactFalse(dst, src, cursor) + case 'n': + return compactNull(dst, src, cursor) + default: + return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor) + } + } +} + +func indentObject( + dst []byte, + src []byte, + indentNum int, + cursor int64, + prefix []byte, + indentBytes []byte, + escape bool) ([]byte, int64, error) { + if src[cursor] == '{' { + dst = append(dst, '{') + } else { + return nil, 0, errors.ErrExpected("expected { character for object value", cursor) + } + cursor = skipWhiteSpace(src, cursor+1) + if src[cursor] == '}' { + dst = append(dst, '}') + return dst, cursor + 1, nil + } + indentNum++ + var err error + for { + dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum)...) + cursor = skipWhiteSpace(src, cursor) + dst, cursor, err = compactString(dst, src, cursor, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + if src[cursor] != ':' { + return nil, 0, errors.ErrSyntax( + fmt.Sprintf("invalid character '%c' after object key", src[cursor]), + cursor+1, + ) + } + dst = append(dst, ':', ' ') + dst, cursor, err = indentValue(dst, src, indentNum, cursor+1, prefix, indentBytes, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + switch src[cursor] { + case '}': + dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum-1)...) + dst = append(dst, '}') + cursor++ + return dst, cursor, nil + case ',': + dst = append(dst, ',') + default: + return nil, 0, errors.ErrSyntax( + fmt.Sprintf("invalid character '%c' after object key:value pair", src[cursor]), + cursor+1, + ) + } + cursor++ + } +} + +func indentArray( + dst []byte, + src []byte, + indentNum int, + cursor int64, + prefix []byte, + indentBytes []byte, + escape bool) ([]byte, int64, error) { + if src[cursor] == '[' { + dst = append(dst, '[') + } else { + return nil, 0, errors.ErrExpected("expected [ character for array value", cursor) + } + cursor = skipWhiteSpace(src, cursor+1) + if src[cursor] == ']' { + dst = append(dst, ']') + return dst, cursor + 1, nil + } + indentNum++ + var err error + for { + dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum)...) + dst, cursor, err = indentValue(dst, src, indentNum, cursor, prefix, indentBytes, escape) + if err != nil { + return nil, 0, err + } + cursor = skipWhiteSpace(src, cursor) + switch src[cursor] { + case ']': + dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum-1)...) + dst = append(dst, ']') + cursor++ + return dst, cursor, nil + case ',': + dst = append(dst, ',') + default: + return nil, 0, errors.ErrSyntax( + fmt.Sprintf("invalid character '%c' after array value", src[cursor]), + cursor+1, + ) + } + cursor++ + } +} diff --git a/internal/encoder/vm/vm.go b/internal/encoder/vm/vm.go index b4b80b6..ac2997a 100644 --- a/internal/encoder/vm/vm.go +++ b/internal/encoder/vm/vm.go @@ -273,7 +273,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if code.IsNilableType && code.Indirect { p = ptrToPtr(p) } - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -2718,7 +2718,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -2762,7 +2762,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -2807,7 +2807,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt code = code.NextField } else { b = append(b, code.Key...) - bb, err := appendMarshalJSON(code, b, iface, false) + bb, err := appendMarshalJSON(ctx, code, b, iface, false) if err != nil { return nil, err } @@ -2847,7 +2847,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -2887,7 +2887,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt code = code.NextField } else { b = append(b, code.Key...) - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -3628,7 +3628,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -3652,7 +3652,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt break } b = append(b, code.Key...) - bb, err := appendMarshalJSON(code, b, iface, false) + bb, err := appendMarshalJSON(ctx, code, b, iface, false) if err != nil { return nil, err } @@ -3665,7 +3665,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -3678,7 +3678,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt p = ptrToNPtr(p+code.Offset, code.PtrNum) if p != 0 { b = append(b, code.Key...) - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } diff --git a/internal/encoder/vm_debug/vm.go b/internal/encoder/vm_debug/vm.go index b78d813..88809f7 100644 --- a/internal/encoder/vm_debug/vm.go +++ b/internal/encoder/vm_debug/vm.go @@ -286,7 +286,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if code.IsNilableType && code.Indirect { p = ptrToPtr(p) } - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -2731,7 +2731,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -2775,7 +2775,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -2820,7 +2820,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt code = code.NextField } else { b = append(b, code.Key...) - bb, err := appendMarshalJSON(code, b, iface, false) + bb, err := appendMarshalJSON(ctx, code, b, iface, false) if err != nil { return nil, err } @@ -2860,7 +2860,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -2900,7 +2900,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt code = code.NextField } else { b = append(b, code.Key...) - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -3641,7 +3641,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -3665,7 +3665,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt break } b = append(b, code.Key...) - bb, err := appendMarshalJSON(code, b, iface, false) + bb, err := appendMarshalJSON(ctx, code, b, iface, false) if err != nil { return nil, err } @@ -3678,7 +3678,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } @@ -3691,7 +3691,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt p = ptrToNPtr(p+code.Offset, code.PtrNum) if p != 0 { b = append(b, code.Key...) - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false) if err != nil { return nil, err } diff --git a/internal/encoder/vm_escaped/vm.go b/internal/encoder/vm_escaped/vm.go index 846119b..1daec9d 100644 --- a/internal/encoder/vm_escaped/vm.go +++ b/internal/encoder/vm_escaped/vm.go @@ -273,7 +273,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if code.IsNilableType && code.Indirect { p = ptrToPtr(p) } - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true) if err != nil { return nil, err } @@ -2718,7 +2718,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true) if err != nil { return nil, err } @@ -2762,7 +2762,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true) if err != nil { return nil, err } @@ -2807,7 +2807,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt code = code.NextField } else { b = append(b, code.EscapedKey...) - bb, err := appendMarshalJSON(code, b, iface, true) + bb, err := appendMarshalJSON(ctx, code, b, iface, true) if err != nil { return nil, err } @@ -2847,7 +2847,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true) if err != nil { return nil, err } @@ -2887,7 +2887,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt code = code.NextField } else { b = append(b, code.EscapedKey...) - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true) if err != nil { return nil, err } @@ -3628,7 +3628,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 && code.Nilcheck { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true) if err != nil { return nil, err } @@ -3652,7 +3652,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt break } b = append(b, code.EscapedKey...) - bb, err := appendMarshalJSON(code, b, iface, true) + bb, err := appendMarshalJSON(ctx, code, b, iface, true) if err != nil { return nil, err } @@ -3665,7 +3665,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt if p == 0 { b = appendNull(b) } else { - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true) if err != nil { return nil, err } @@ -3678,7 +3678,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt p = ptrToNPtr(p+code.Offset, code.PtrNum) if p != 0 { b = append(b, code.EscapedKey...) - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true) if err != nil { return nil, err } diff --git a/json_test.go b/json_test.go index 9470ed1..47a6c38 100644 --- a/json_test.go +++ b/json_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + stdjson "encoding/json" "math" "math/rand" "reflect" @@ -80,6 +81,37 @@ func TestCompact(t *testing.T) { t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact) } } + t.Run("invalid", func(t *testing.T) { + for _, src := range []string{ + `invalid`, + `}`, + `]`, + `{"a":1}}`, + `{"a" 1}`, + `{"a": 1 "b": 2}`, + `["a" "b"]`, + `"\`, + `{"a":"\\""}`, + `tr`, + `{"a": tru, "b": 1}`, + `fal`, + `{"a": fals, "b": 1}`, + `nu`, + `{"a": nul, "b": 1}`, + `1.234.567`, + `[nul]`, + `{} 1`, + } { + buf.Reset() + if err := stdjson.Compact(&buf, []byte(src)); err == nil { + t.Fatal("invalid test case") + } + buf.Reset() + if err := json.Compact(&buf, []byte(src)); err == nil { + t.Fatalf("%q: expected error", src) + } + } + }) } func TestCompactSeparators(t *testing.T) { @@ -119,6 +151,37 @@ func TestIndent(t *testing.T) { t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent) } } + t.Run("invalid", func(t *testing.T) { + for _, src := range []string{ + `invalid`, + `}`, + `]`, + `{"a":1}}`, + `{"a" 1}`, + `{"a": 1 "b": 2}`, + `["a" "b"]`, + `"\`, + `{"a":"\\""}`, + `tr`, + `{"a": tru, "b": 1}`, + `fal`, + `{"a": fals, "b": 1}`, + `nu`, + `{"a": nul, "b": 1}`, + `1.234.567`, + `[nul]`, + `{} 1`, + } { + buf.Reset() + if err := stdjson.Indent(&buf, []byte(src), "", " "); err == nil { + t.Fatal("invalid test case") + } + buf.Reset() + if err := json.Indent(&buf, []byte(src), "", " "); err == nil { + t.Fatalf("%q: expected error", src) + } + } + }) } // Tests of a large random structure. @@ -191,7 +254,7 @@ func TestIndentErrors(t *testing.T) { buf := bytes.NewBuffer(slice) if err := json.Indent(buf, []uint8(tt.in), "", ""); err != nil { if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: Indent: %#v", i, err) + t.Errorf("#%d: Indent: expected %#v but got %#v", i, tt.err, err) continue } }