forked from mirror/go-json
Merge pull request #189 from goccy/feature/fix-compact
Fix Compact/Indent API
This commit is contained in:
commit
e66e5606b5
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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++
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
65
json_test.go
65
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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue