Skip to content

Commit aa8099d

Browse files
authored
Merge pull request #24 from goccy/feature/fix-compact
Fix Compact/Indent
2 parents 901128a + 7fa9ad1 commit aa8099d

File tree

10 files changed

+449
-30
lines changed

10 files changed

+449
-30
lines changed

compact.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package json
2+
3+
import (
4+
"bytes"
5+
)
6+
7+
func compact(dst *bytes.Buffer, src []byte) error {
8+
length := len(src)
9+
for cursor := 0; cursor < length; cursor++ {
10+
c := src[cursor]
11+
switch c {
12+
case ' ', '\t', '\n', '\r':
13+
continue
14+
case '"':
15+
if err := dst.WriteByte(c); err != nil {
16+
return err
17+
}
18+
for {
19+
cursor++
20+
if err := dst.WriteByte(src[cursor]); err != nil {
21+
return err
22+
}
23+
switch src[cursor] {
24+
case '\\':
25+
cursor++
26+
if err := dst.WriteByte(src[cursor]); err != nil {
27+
return err
28+
}
29+
case '"':
30+
goto LOOP_END
31+
case nul:
32+
return errUnexpectedEndOfJSON("string", int64(length))
33+
}
34+
}
35+
default:
36+
if err := dst.WriteByte(c); err != nil {
37+
return err
38+
}
39+
}
40+
LOOP_END:
41+
}
42+
return nil
43+
}

decode_bool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func trueBytes(s *stream) error {
3434

3535
func falseBytes(s *stream) error {
3636
if s.cursor+4 >= s.length {
37-
if s.read() {
37+
if !s.read() {
3838
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
3939
}
4040
}

decode_interface.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,85 @@ var (
3333
)
3434
)
3535

36+
var (
37+
hexToInt = [256]int{
38+
'0': 0,
39+
'1': 1,
40+
'2': 2,
41+
'3': 3,
42+
'4': 4,
43+
'5': 5,
44+
'6': 6,
45+
'7': 7,
46+
'8': 8,
47+
'9': 9,
48+
'A': 10,
49+
'B': 11,
50+
'C': 12,
51+
'D': 13,
52+
'E': 14,
53+
'F': 15,
54+
'a': 10,
55+
'b': 11,
56+
'c': 12,
57+
'd': 13,
58+
'e': 14,
59+
'f': 15,
60+
}
61+
)
62+
63+
func unicodeToRune(code []byte) rune {
64+
sum := 0
65+
for i := 0; i < len(code); i++ {
66+
sum += hexToInt[code[i]] << (uint(len(code)-i-1) * 4)
67+
}
68+
return rune(sum)
69+
}
70+
71+
func decodeEscapeString(s *stream) error {
72+
s.cursor++
73+
RETRY:
74+
switch s.buf[s.cursor] {
75+
case '"':
76+
s.buf[s.cursor] = '"'
77+
case '\\':
78+
s.buf[s.cursor] = '\\'
79+
case '/':
80+
s.buf[s.cursor] = '/'
81+
case 'b':
82+
s.buf[s.cursor] = '\b'
83+
case 'f':
84+
s.buf[s.cursor] = '\f'
85+
case 'n':
86+
s.buf[s.cursor] = '\n'
87+
case 'r':
88+
s.buf[s.cursor] = '\r'
89+
case 't':
90+
s.buf[s.cursor] = '\t'
91+
case 'u':
92+
if s.cursor+5 >= s.length {
93+
if !s.read() {
94+
return errInvalidCharacter(s.char(), "escaped string", s.totalOffset())
95+
}
96+
}
97+
code := unicodeToRune(s.buf[s.cursor+1 : s.cursor+5])
98+
unicode := []byte(string(code))
99+
s.buf = append(append(s.buf[:s.cursor-1], unicode...), s.buf[s.cursor+5:]...)
100+
s.cursor--
101+
return nil
102+
case nul:
103+
if !s.read() {
104+
return errInvalidCharacter(s.char(), "escaped string", s.totalOffset())
105+
}
106+
goto RETRY
107+
default:
108+
return errUnexpectedEndOfJSON("string", s.totalOffset())
109+
}
110+
s.buf = append(s.buf[:s.cursor-1], s.buf[s.cursor:]...)
111+
s.cursor--
112+
return nil
113+
}
114+
36115
func (d *interfaceDecoder) decodeStream(s *stream, p uintptr) error {
37116
s.skipWhiteSpace()
38117
for {
@@ -71,7 +150,9 @@ func (d *interfaceDecoder) decodeStream(s *stream, p uintptr) error {
71150
for {
72151
switch s.char() {
73152
case '\\':
74-
s.cursor++
153+
if err := decodeEscapeString(s); err != nil {
154+
return err
155+
}
75156
case '"':
76157
literal := s.buf[start:s.cursor]
77158
s.cursor++

decode_map.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (d *mapDecoder) decodeStream(s *stream, p uintptr) error {
9999
return nil
100100
}
101101
if s.char() != ',' {
102-
return errExpected("semicolon after object value", s.totalOffset())
102+
return errExpected("comma after object value", s.totalOffset())
103103
}
104104
}
105105
return nil
@@ -168,7 +168,7 @@ func (d *mapDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, error)
168168
return cursor, nil
169169
}
170170
if buf[cursor] != ',' {
171-
return 0, errExpected("semicolon after object value", cursor)
171+
return 0, errExpected("comma after object value", cursor)
172172
}
173173
}
174174
return cursor, nil

decode_stream.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,25 @@ func (s *stream) read() bool {
6767
if n < readChunkSize || err == io.EOF {
6868
s.allRead = true
6969
}
70-
totalSize := s.length + int64(n) + 1
70+
// extend buffer (2) is protect ( s.cursor++ x2 )
71+
// e.g.) decodeEscapeString
72+
const extendBufLength = int64(2)
73+
74+
totalSize := s.length + int64(n) + extendBufLength
7175
if totalSize > readChunkSize {
7276
newBuf := make([]byte, totalSize)
7377
copy(newBuf, s.buf)
7478
copy(newBuf[s.length:], buf)
7579
s.buf = newBuf
76-
s.length = totalSize - 1
80+
s.length = totalSize - extendBufLength
7781
} else if s.length > 0 {
7882
copy(buf[s.length:], buf)
7983
copy(buf, s.buf[:s.length])
8084
s.buf = buf
81-
s.length = totalSize - 1
85+
s.length = totalSize - extendBufLength
8286
} else {
8387
s.buf = buf
84-
s.length = totalSize - 1
88+
s.length = totalSize - extendBufLength
8589
}
8690
s.offset += s.cursor
8791
if n == 0 {

encode_vm.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,19 @@ func (e *Encoder) run(code *opcode) error {
8484
typ = typ.Elem()
8585
}
8686
e.indent = ifaceCode.indent
87-
c, err := e.compile(typ, ifaceCode.root, e.enabledIndent)
88-
if err != nil {
89-
return err
87+
var c *opcode
88+
if typ.Kind() == reflect.Map {
89+
code, err := e.compileMap(typ, false, ifaceCode.root, e.enabledIndent)
90+
if err != nil {
91+
return err
92+
}
93+
c = code
94+
} else {
95+
code, err := e.compile(typ, ifaceCode.root, e.enabledIndent)
96+
if err != nil {
97+
return err
98+
}
99+
c = code
90100
}
91101
c.ptr = uintptr(header.ptr)
92102
c.beforeLastCode().next = code.next

export_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package json
2+
3+
func NewSyntaxError(msg string, offset int64) *SyntaxError {
4+
return &SyntaxError{
5+
msg: msg,
6+
Offset: offset,
7+
}
8+
}

indent.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package json
2+
3+
import "bytes"
4+
5+
func encodeWithIndent(dst *bytes.Buffer, src []byte, prefix, indentStr string) error {
6+
length := int64(len(src))
7+
indentNum := 0
8+
indentBytes := []byte(indentStr)
9+
for cursor := int64(0); cursor < length; cursor++ {
10+
c := src[cursor]
11+
switch c {
12+
case ' ', '\t', '\n', '\r':
13+
continue
14+
case '"':
15+
if err := dst.WriteByte(c); err != nil {
16+
return err
17+
}
18+
for {
19+
cursor++
20+
if err := dst.WriteByte(src[cursor]); err != nil {
21+
return err
22+
}
23+
switch src[cursor] {
24+
case '\\':
25+
cursor++
26+
if err := dst.WriteByte(src[cursor]); err != nil {
27+
return err
28+
}
29+
case '"':
30+
goto LOOP_END
31+
case nul:
32+
return errUnexpectedEndOfJSON("string", int64(length))
33+
}
34+
}
35+
case '{':
36+
if cursor+1 < length && src[cursor+1] == '}' {
37+
if _, err := dst.Write([]byte{'{', '}'}); err != nil {
38+
return err
39+
}
40+
cursor++
41+
} else {
42+
indentNum++
43+
b := []byte{c, '\n'}
44+
b = append(b, prefix...)
45+
b = append(b, bytes.Repeat(indentBytes, indentNum)...)
46+
if _, err := dst.Write(b); err != nil {
47+
return err
48+
}
49+
}
50+
case '}':
51+
indentNum--
52+
if indentNum < 0 {
53+
return errInvalidCharacter('}', "}", cursor)
54+
}
55+
b := []byte{'\n', c}
56+
b = append(b, prefix...)
57+
b = append(b, bytes.Repeat(indentBytes, indentNum)...)
58+
if _, err := dst.Write(b); err != nil {
59+
return err
60+
}
61+
case '[':
62+
if cursor+1 < length && src[cursor+1] == ']' {
63+
if _, err := dst.Write([]byte{'[', ']'}); err != nil {
64+
return err
65+
}
66+
cursor++
67+
} else {
68+
indentNum++
69+
b := []byte{c, '\n'}
70+
b = append(b, prefix...)
71+
b = append(b, bytes.Repeat(indentBytes, indentNum)...)
72+
if _, err := dst.Write(b); err != nil {
73+
return err
74+
}
75+
}
76+
case ']':
77+
indentNum--
78+
if indentNum < 0 {
79+
return errInvalidCharacter(']', "]", cursor)
80+
}
81+
b := []byte{'\n', c}
82+
b = append(b, prefix...)
83+
b = append(b, bytes.Repeat(indentBytes, indentNum)...)
84+
if _, err := dst.Write(b); err != nil {
85+
return err
86+
}
87+
case ':':
88+
if _, err := dst.Write([]byte{':', ' '}); err != nil {
89+
return err
90+
}
91+
case ',':
92+
b := []byte{',', '\n'}
93+
b = append(b, prefix...)
94+
b = append(b, bytes.Repeat(indentBytes, indentNum)...)
95+
if _, err := dst.Write(b); err != nil {
96+
return err
97+
}
98+
default:
99+
if err := dst.WriteByte(c); err != nil {
100+
return err
101+
}
102+
}
103+
LOOP_END:
104+
}
105+
return nil
106+
}

json.go

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -325,15 +325,7 @@ func (m *RawMessage) UnmarshalJSON(data []byte) error {
325325
// Compact appends to dst the JSON-encoded src with
326326
// insignificant space characters elided.
327327
func Compact(dst *bytes.Buffer, src []byte) error {
328-
var v interface{}
329-
dec := NewDecoder(bytes.NewBuffer(src))
330-
dec.UseNumber()
331-
if err := dec.Decode(&v); err != nil {
332-
return err
333-
}
334-
enc := NewEncoder(dst)
335-
enc.SetEscapeHTML(false)
336-
return enc.Encode(v)
328+
return compact(dst, src)
337329
}
338330

339331
// Indent appends to dst an indented form of the JSON-encoded src.
@@ -348,16 +340,7 @@ func Compact(dst *bytes.Buffer, src []byte) error {
348340
// For example, if src has no trailing spaces, neither will dst;
349341
// if src ends in a trailing newline, so will dst.
350342
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
351-
var v interface{}
352-
dec := NewDecoder(bytes.NewBuffer(src))
353-
dec.UseNumber()
354-
if err := dec.Decode(&v); err != nil {
355-
return err
356-
}
357-
enc := NewEncoder(dst)
358-
enc.SetEscapeHTML(false)
359-
enc.SetIndent(prefix, indent)
360-
return enc.Encode(v)
343+
return encodeWithIndent(dst, src, prefix, indent)
361344
}
362345

363346
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029

0 commit comments

Comments
 (0)