Merge pull request #189 from goccy/feature/fix-compact

Fix Compact/Indent API
This commit is contained in:
Masaaki Goshima 2021-04-19 23:21:11 +09:00 committed by GitHub
commit e66e5606b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 597 additions and 189 deletions

View File

@ -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)
}
}
}

View File

@ -2,55 +2,272 @@ package encoder
import ( import (
"bytes" "bytes"
"fmt"
"strconv"
"unsafe"
"github.com/goccy/go-json/internal/errors" "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 { if len(src) == 0 {
return errors.ErrUnexpectedEndOfJSON("", 0) return errors.ErrUnexpectedEndOfJSON("", 0)
} }
length := len(src) buf.Grow(len(src))
for cursor := 0; cursor < length; cursor++ { dst := buf.Bytes()
c := src[cursor] newSrc := make([]byte, len(src)+1) // append nul byte to the end
switch c { copy(newSrc, src)
case ' ', '\t', '\n', '\r': dst, err := compact(dst, newSrc, escape)
continue if err != nil {
case '"':
if err := dst.WriteByte(c); err != nil {
return err 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 { for {
cursor++ cursor++
c := src[cursor] c := src[cursor]
if escape && (c == '<' || c == '>' || c == '&') { if escape {
if _, err := dst.WriteString(`\u00`); err != nil { if isHTMLEscapeChar[c] {
return err 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
} }
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 { switch c {
case '\\': case '\\':
cursor++ cursor++
if err := dst.WriteByte(src[cursor]); err != nil { if src[cursor] == nul {
return err return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
} }
case '"': case '"':
goto LOOP_END cursor++
case '\000': return append(dst, src[start:cursor]...), cursor, nil
return errors.ErrUnexpectedEndOfJSON("string", int64(length)) case nul:
return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
} }
} }
default:
if err := dst.WriteByte(c); err != nil {
return err
} }
func compactNumber(dst, src []byte, cursor int64) ([]byte, int64, error) {
start := cursor
for {
cursor++
if floatTable[src[cursor]] {
continue
} }
LOOP_END: break
} }
return nil 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
} }

View File

@ -306,6 +306,7 @@ func MapLen(m unsafe.Pointer) int
type RuntimeContext struct { type RuntimeContext struct {
Buf []byte Buf []byte
MarshalBuf []byte
Ptrs []uintptr Ptrs []uintptr
KeepRefs []unsafe.Pointer KeepRefs []unsafe.Pointer
SeenPtr []uintptr SeenPtr []uintptr
@ -413,7 +414,7 @@ func AppendNumber(b []byte, n json.Number) ([]byte, error) {
return b, nil 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 rv := reflect.ValueOf(v) // convert by dynamic interface type
if code.AddrForMarshaler { if code.AddrForMarshaler {
if rv.CanAddr() { if rv.CanAddr() {
@ -433,12 +434,14 @@ func AppendMarshalJSON(code *Opcode, b []byte, v interface{}, escape bool) ([]by
if err != nil { if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
} }
buf := bytes.NewBuffer(b) marshalBuf := ctx.MarshalBuf[:0]
// TODO: we should validate buffer with `compact` marshalBuf = append(append(marshalBuf, bb...), nul)
if err := Compact(buf, bb, escape); err != nil { compactedBuf, err := compact(b, marshalBuf, escape)
if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} 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) { 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 { if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
} }
var compactBuf bytes.Buffer marshalBuf := ctx.MarshalBuf[:0]
if err := Compact(&compactBuf, bb, escape); err != nil { marshalBuf = append(append(marshalBuf, bb...), nul)
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} indentedBuf, err := doIndent(
} b,
var indentBuf bytes.Buffer marshalBuf,
if err := Indent(
&indentBuf,
compactBuf.Bytes(),
string(ctx.Prefix)+strings.Repeat(string(ctx.IndentStr), ctx.BaseIndent+indent), string(ctx.Prefix)+strings.Repeat(string(ctx.IndentStr), ctx.BaseIndent+indent),
string(ctx.IndentStr), string(ctx.IndentStr),
); err != nil { escape,
)
if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err} 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) { func AppendMarshalText(code *Opcode, b []byte, v interface{}, escape bool) ([]byte, error) {

View File

@ -2,111 +2,176 @@ package encoder
import ( import (
"bytes" "bytes"
"fmt"
"github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/errors"
) )
func Indent(dst *bytes.Buffer, src []byte, prefix, indentStr string) error { func Indent(buf *bytes.Buffer, src []byte, prefix, indentStr string) error {
length := int64(len(src)) if len(src) == 0 {
indentNum := 0 return errors.ErrUnexpectedEndOfJSON("", 0)
indentBytes := []byte(indentStr) }
for cursor := int64(0); cursor < length; cursor++ { buf.Grow(len(src))
c := src[cursor] dst := buf.Bytes()
switch c { newSrc := make([]byte, len(src)+1) // append nul byte to the end
case ' ', '\t', '\n', '\r': copy(newSrc, src)
continue dst, err := doIndent(dst, newSrc, prefix, indentStr, false)
case '"': if err != nil {
if err := dst.WriteByte(c); err != nil {
return err return err
} }
for { if _, err := buf.Write(dst); err != nil {
cursor++
if err := dst.WriteByte(src[cursor]); err != nil {
return err 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:
}
return nil 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++
}
}

View File

@ -273,7 +273,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if code.IsNilableType && code.Indirect { if code.IsNilableType && code.Indirect {
p = ptrToPtr(p) 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 { if err != nil {
return nil, err return nil, err
} }
@ -2718,7 +2718,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2762,7 +2762,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2807,7 +2807,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
code = code.NextField code = code.NextField
} else { } else {
b = append(b, code.Key...) b = append(b, code.Key...)
bb, err := appendMarshalJSON(code, b, iface, false) bb, err := appendMarshalJSON(ctx, code, b, iface, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2847,7 +2847,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 { if p == 0 {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2887,7 +2887,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
code = code.NextField code = code.NextField
} else { } else {
b = append(b, code.Key...) 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 { if err != nil {
return nil, err return nil, err
} }
@ -3628,7 +3628,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3652,7 +3652,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
break break
} }
b = append(b, code.Key...) b = append(b, code.Key...)
bb, err := appendMarshalJSON(code, b, iface, false) bb, err := appendMarshalJSON(ctx, code, b, iface, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3665,7 +3665,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 { if p == 0 {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err 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) p = ptrToNPtr(p+code.Offset, code.PtrNum)
if p != 0 { if p != 0 {
b = append(b, code.Key...) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -286,7 +286,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if code.IsNilableType && code.Indirect { if code.IsNilableType && code.Indirect {
p = ptrToPtr(p) 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 { if err != nil {
return nil, err return nil, err
} }
@ -2731,7 +2731,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2775,7 +2775,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2820,7 +2820,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
code = code.NextField code = code.NextField
} else { } else {
b = append(b, code.Key...) b = append(b, code.Key...)
bb, err := appendMarshalJSON(code, b, iface, false) bb, err := appendMarshalJSON(ctx, code, b, iface, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2860,7 +2860,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 { if p == 0 {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2900,7 +2900,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
code = code.NextField code = code.NextField
} else { } else {
b = append(b, code.Key...) 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 { if err != nil {
return nil, err return nil, err
} }
@ -3641,7 +3641,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3665,7 +3665,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
break break
} }
b = append(b, code.Key...) b = append(b, code.Key...)
bb, err := appendMarshalJSON(code, b, iface, false) bb, err := appendMarshalJSON(ctx, code, b, iface, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3678,7 +3678,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 { if p == 0 {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), false) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), false)
if err != nil { if err != nil {
return nil, err 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) p = ptrToNPtr(p+code.Offset, code.PtrNum)
if p != 0 { if p != 0 {
b = append(b, code.Key...) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -273,7 +273,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if code.IsNilableType && code.Indirect { if code.IsNilableType && code.Indirect {
p = ptrToPtr(p) 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 { if err != nil {
return nil, err return nil, err
} }
@ -2718,7 +2718,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2762,7 +2762,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2807,7 +2807,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
code = code.NextField code = code.NextField
} else { } else {
b = append(b, code.EscapedKey...) b = append(b, code.EscapedKey...)
bb, err := appendMarshalJSON(code, b, iface, true) bb, err := appendMarshalJSON(ctx, code, b, iface, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2847,7 +2847,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 { if p == 0 {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2887,7 +2887,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
code = code.NextField code = code.NextField
} else { } else {
b = append(b, code.EscapedKey...) 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 { if err != nil {
return nil, err return nil, err
} }
@ -3628,7 +3628,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 && code.Nilcheck { if p == 0 && code.Nilcheck {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3652,7 +3652,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
break break
} }
b = append(b, code.EscapedKey...) b = append(b, code.EscapedKey...)
bb, err := appendMarshalJSON(code, b, iface, true) bb, err := appendMarshalJSON(ctx, code, b, iface, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3665,7 +3665,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
if p == 0 { if p == 0 {
b = appendNull(b) b = appendNull(b)
} else { } else {
bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) bb, err := appendMarshalJSON(ctx, code, b, ptrToInterface(code, p), true)
if err != nil { if err != nil {
return nil, err 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) p = ptrToNPtr(p+code.Offset, code.PtrNum)
if p != 0 { if p != 0 {
b = append(b, code.EscapedKey...) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,6 +2,7 @@ package json_test
import ( import (
"bytes" "bytes"
stdjson "encoding/json"
"math" "math"
"math/rand" "math/rand"
"reflect" "reflect"
@ -80,6 +81,37 @@ func TestCompact(t *testing.T) {
t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact) 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) { 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.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. // Tests of a large random structure.
@ -191,7 +254,7 @@ func TestIndentErrors(t *testing.T) {
buf := bytes.NewBuffer(slice) buf := bytes.NewBuffer(slice)
if err := json.Indent(buf, []uint8(tt.in), "", ""); err != nil { if err := json.Indent(buf, []uint8(tt.in), "", ""); err != nil {
if !reflect.DeepEqual(err, tt.err) { 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 continue
} }
} }