mirror of https://github.com/goccy/go-json.git
Fix Indent ( validate the input buffer )
This commit is contained in:
parent
7565171124
commit
34396640d5
|
@ -464,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) {
|
||||||
|
|
|
@ -2,111 +2,179 @@ 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
|
}
|
||||||
}
|
if _, err := buf.Write(dst); err != nil {
|
||||||
for {
|
return err
|
||||||
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:
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doIndent(dst, src []byte, prefix, indentStr string, escape bool) ([]byte, error) {
|
||||||
|
buf, _, err := indentValue(dst, src, 0, 0, []byte(prefix), []byte(indentStr), escape)
|
||||||
|
if 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) {
|
||||||
|
switch src[cursor] {
|
||||||
|
case 'n':
|
||||||
|
return compactNull(dst, src, cursor)
|
||||||
|
case '{':
|
||||||
|
dst = append(dst, '{')
|
||||||
|
default:
|
||||||
|
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) {
|
||||||
|
switch src[cursor] {
|
||||||
|
case 'n':
|
||||||
|
return compactNull(dst, src, cursor)
|
||||||
|
case '[':
|
||||||
|
dst = append(dst, '[')
|
||||||
|
default:
|
||||||
|
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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -197,7 +197,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue