Fix Compact

This commit is contained in:
Masaaki Goshima 2020-08-13 01:21:10 +09:00
parent 901128a986
commit cff09d2448
5 changed files with 211 additions and 8 deletions

View File

@ -34,7 +34,7 @@ func trueBytes(s *stream) error {
func falseBytes(s *stream) error { func falseBytes(s *stream) error {
if s.cursor+4 >= s.length { if s.cursor+4 >= s.length {
if s.read() { if !s.read() {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset()) return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
} }
} }

View File

@ -67,21 +67,25 @@ func (s *stream) read() bool {
if n < readChunkSize || err == io.EOF { if n < readChunkSize || err == io.EOF {
s.allRead = true s.allRead = true
} }
totalSize := s.length + int64(n) + 1 // extend buffer (2) is protect ( s.cursor++ x2 )
// e.g.) line 85 in decode_interface.go
const extendBufLength = int64(2)
totalSize := s.length + int64(n) + extendBufLength
if totalSize > readChunkSize { if totalSize > readChunkSize {
newBuf := make([]byte, totalSize) newBuf := make([]byte, totalSize)
copy(newBuf, s.buf) copy(newBuf, s.buf)
copy(newBuf[s.length:], buf) copy(newBuf[s.length:], buf)
s.buf = newBuf s.buf = newBuf
s.length = totalSize - 1 s.length = totalSize - extendBufLength
} else if s.length > 0 { } else if s.length > 0 {
copy(buf[s.length:], buf) copy(buf[s.length:], buf)
copy(buf, s.buf[:s.length]) copy(buf, s.buf[:s.length])
s.buf = buf s.buf = buf
s.length = totalSize - 1 s.length = totalSize - extendBufLength
} else { } else {
s.buf = buf s.buf = buf
s.length = totalSize - 1 s.length = totalSize - extendBufLength
} }
s.offset += s.cursor s.offset += s.cursor
if n == 0 { if n == 0 {

View File

@ -84,10 +84,20 @@ func (e *Encoder) run(code *opcode) error {
typ = typ.Elem() typ = typ.Elem()
} }
e.indent = ifaceCode.indent e.indent = ifaceCode.indent
c, err := e.compile(typ, ifaceCode.root, e.enabledIndent) var c *opcode
if typ.Kind() == reflect.Map {
code, err := e.compileMap(typ, false, ifaceCode.root, e.enabledIndent)
if err != nil { if err != nil {
return err return err
} }
c = code
} else {
code, err := e.compile(typ, ifaceCode.root, e.enabledIndent)
if err != nil {
return err
}
c = code
}
c.ptr = uintptr(header.ptr) c.ptr = uintptr(header.ptr)
c.beforeLastCode().next = code.next c.beforeLastCode().next = code.next
code = c code = c

8
export_test.go Normal file
View File

@ -0,0 +1,8 @@
package json
func NewSyntaxError(msg string, offset int64) *SyntaxError {
return &SyntaxError{
msg: msg,
Offset: offset,
}
}

View File

@ -2,6 +2,9 @@ package json_test
import ( import (
"bytes" "bytes"
"math"
"math/rand"
"reflect"
"testing" "testing"
"github.com/goccy/go-json" "github.com/goccy/go-json"
@ -116,3 +119,181 @@ func TestIndent(t *testing.T) {
} }
} }
} }
// Tests of a large random structure.
func TestCompactBig(t *testing.T) {
initBig()
var buf bytes.Buffer
if err := json.Compact(&buf, jsonBig); err != nil {
t.Fatalf("Compact: %v", err)
}
b := buf.Bytes()
if !bytes.Equal(b, jsonBig) {
t.Error("Compact(jsonBig) != jsonBig")
diff(t, b, jsonBig)
return
}
}
func TestIndentBig(t *testing.T) {
t.Parallel()
initBig()
var buf bytes.Buffer
if err := json.Indent(&buf, jsonBig, "", "\t"); err != nil {
t.Fatalf("Indent1: %v", err)
}
b := buf.Bytes()
if len(b) == len(jsonBig) {
// jsonBig is compact (no unnecessary spaces);
// indenting should make it bigger
t.Fatalf("Indent(jsonBig) did not get bigger")
}
// should be idempotent
var buf1 bytes.Buffer
if err := json.Indent(&buf1, b, "", "\t"); err != nil {
t.Fatalf("Indent2: %v", err)
}
b1 := buf1.Bytes()
if !bytes.Equal(b1, b) {
t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)")
diff(t, b1, b)
return
}
// should get back to original
buf1.Reset()
if err := json.Compact(&buf1, b); err != nil {
t.Fatalf("Compact: %v", err)
}
b1 = buf1.Bytes()
if !bytes.Equal(b1, jsonBig) {
t.Error("Compact(Indent(jsonBig)) != jsonBig")
diff(t, b1, jsonBig)
return
}
}
type indentErrorTest struct {
in string
err error
}
var indentErrorTests = []indentErrorTest{
{`{"X": "foo", "Y"}`, json.NewSyntaxError("invalid character '}' after object key", 17)},
{`{"X": "foo" "Y": "bar"}`, json.NewSyntaxError("invalid character '\"' after object key:value pair", 13)},
}
func TestIndentErrors(t *testing.T) {
for i, tt := range indentErrorTests {
slice := make([]uint8, 0)
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)
continue
}
}
}
}
func diff(t *testing.T, a, b []byte) {
for i := 0; ; i++ {
if i >= len(a) || i >= len(b) || a[i] != b[i] {
j := i - 10
if j < 0 {
j = 0
}
t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:]))
return
}
}
}
func trim(b []byte) []byte {
if len(b) > 20 {
return b[0:20]
}
return b
}
// Generate a random JSON object.
var jsonBig []byte
func initBig() {
n := 10000
if testing.Short() {
n = 100
}
v := genValue(n)
b, err := json.Marshal(v)
if err != nil {
panic(err)
}
jsonBig = b
}
func genValue(n int) interface{} {
if n > 1 {
switch rand.Intn(2) {
case 0:
return genArray(n)
case 1:
return genMap(n)
}
}
switch rand.Intn(3) {
case 0:
return rand.Intn(2) == 0
case 1:
return rand.NormFloat64()
case 2:
return genString(30)
}
panic("unreachable")
}
func genString(stddev float64) string {
n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2))
c := make([]rune, n)
for i := range c {
f := math.Abs(rand.NormFloat64()*64 + 32)
if f > 0x10ffff {
f = 0x10ffff
}
c[i] = rune(f)
}
return string(c)
}
func genArray(n int) []interface{} {
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
if f > n {
f = n
}
if f < 1 {
f = 1
}
x := make([]interface{}, f)
for i := range x {
x[i] = genValue(((i+1)*n)/f - (i*n)/f)
}
return x
}
func genMap(n int) map[string]interface{} {
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
if f > n {
f = n
}
if n > 0 && f == 0 {
f = 1
}
x := make(map[string]interface{})
for i := 0; i < f; i++ {
x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f)
}
return x
}