forked from mirror/go-json
Merge pull request #190 from goccy/feature/improve-indent-compact
Optimize json.Compact and json.Indent
This commit is contained in:
commit
b1431c0aae
47
encode.go
47
encode.go
|
@ -2,7 +2,6 @@ package json
|
|||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/goccy/go-json/internal/encoder"
|
||||
|
@ -22,10 +21,6 @@ type Encoder struct {
|
|||
indentStr string
|
||||
}
|
||||
|
||||
const (
|
||||
bufSize = 1024
|
||||
)
|
||||
|
||||
type EncodeOption int
|
||||
|
||||
const (
|
||||
|
@ -35,26 +30,6 @@ const (
|
|||
EncodeOptionDebug
|
||||
)
|
||||
|
||||
var (
|
||||
encRuntimeContextPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &encoder.RuntimeContext{
|
||||
Buf: make([]byte, 0, bufSize),
|
||||
Ptrs: make([]uintptr, 128),
|
||||
KeepRefs: make([]unsafe.Pointer, 0, 8),
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func takeEncodeRuntimeContext() *encoder.RuntimeContext {
|
||||
return encRuntimeContextPool.Get().(*encoder.RuntimeContext)
|
||||
}
|
||||
|
||||
func releaseEncodeRuntimeContext(ctx *encoder.RuntimeContext) {
|
||||
encRuntimeContextPool.Put(ctx)
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{w: w, enabledHTMLEscape: true}
|
||||
|
@ -69,11 +44,11 @@ func (e *Encoder) Encode(v interface{}) error {
|
|||
|
||||
// EncodeWithOption call Encode with EncodeOption.
|
||||
func (e *Encoder) EncodeWithOption(v interface{}, optFuncs ...EncodeOptionFunc) error {
|
||||
ctx := takeEncodeRuntimeContext()
|
||||
ctx := encoder.TakeRuntimeContext()
|
||||
|
||||
err := e.encodeWithOption(ctx, v, optFuncs...)
|
||||
|
||||
releaseEncodeRuntimeContext(ctx)
|
||||
encoder.ReleaseRuntimeContext(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -130,11 +105,11 @@ func (e *Encoder) SetIndent(prefix, indent string) {
|
|||
}
|
||||
|
||||
func marshal(v interface{}, opt EncodeOption) ([]byte, error) {
|
||||
ctx := takeEncodeRuntimeContext()
|
||||
ctx := encoder.TakeRuntimeContext()
|
||||
|
||||
buf, err := encode(ctx, v, opt|EncodeOptionHTMLEscape)
|
||||
if err != nil {
|
||||
releaseEncodeRuntimeContext(ctx)
|
||||
encoder.ReleaseRuntimeContext(ctx)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -146,16 +121,16 @@ func marshal(v interface{}, opt EncodeOption) ([]byte, error) {
|
|||
copied := make([]byte, len(buf))
|
||||
copy(copied, buf)
|
||||
|
||||
releaseEncodeRuntimeContext(ctx)
|
||||
encoder.ReleaseRuntimeContext(ctx)
|
||||
return copied, nil
|
||||
}
|
||||
|
||||
func marshalNoEscape(v interface{}, opt EncodeOption) ([]byte, error) {
|
||||
ctx := takeEncodeRuntimeContext()
|
||||
ctx := encoder.TakeRuntimeContext()
|
||||
|
||||
buf, err := encodeNoEscape(ctx, v, opt|EncodeOptionHTMLEscape)
|
||||
if err != nil {
|
||||
releaseEncodeRuntimeContext(ctx)
|
||||
encoder.ReleaseRuntimeContext(ctx)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -167,16 +142,16 @@ func marshalNoEscape(v interface{}, opt EncodeOption) ([]byte, error) {
|
|||
copied := make([]byte, len(buf))
|
||||
copy(copied, buf)
|
||||
|
||||
releaseEncodeRuntimeContext(ctx)
|
||||
encoder.ReleaseRuntimeContext(ctx)
|
||||
return copied, nil
|
||||
}
|
||||
|
||||
func marshalIndent(v interface{}, prefix, indent string, opt EncodeOption) ([]byte, error) {
|
||||
ctx := takeEncodeRuntimeContext()
|
||||
ctx := encoder.TakeRuntimeContext()
|
||||
|
||||
buf, err := encodeIndent(ctx, v, prefix, indent, opt|EncodeOptionHTMLEscape)
|
||||
if err != nil {
|
||||
releaseEncodeRuntimeContext(ctx)
|
||||
encoder.ReleaseRuntimeContext(ctx)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -184,7 +159,7 @@ func marshalIndent(v interface{}, prefix, indent string, opt EncodeOption) ([]by
|
|||
copied := make([]byte, len(buf))
|
||||
copy(copied, buf)
|
||||
|
||||
releaseEncodeRuntimeContext(ctx)
|
||||
encoder.ReleaseRuntimeContext(ctx)
|
||||
return copied, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -30,9 +30,22 @@ func Compact(buf *bytes.Buffer, src []byte, escape bool) error {
|
|||
}
|
||||
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)
|
||||
|
||||
ctx := TakeRuntimeContext()
|
||||
ctxBuf := ctx.Buf[:0]
|
||||
ctxBuf = append(append(ctxBuf, src...), nul)
|
||||
ctx.Buf = ctxBuf
|
||||
|
||||
if err := compactAndWrite(buf, dst, ctxBuf, escape); err != nil {
|
||||
ReleaseRuntimeContext(ctx)
|
||||
return err
|
||||
}
|
||||
ReleaseRuntimeContext(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func compactAndWrite(buf *bytes.Buffer, dst []byte, src []byte, escape bool) error {
|
||||
dst, err := compact(dst, src, escape)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package encoder
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/goccy/go-json/internal/runtime"
|
||||
)
|
||||
|
||||
|
@ -80,3 +83,53 @@ func (c *compileContext) decPtrIndex() {
|
|||
c.parent.decPtrIndex()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
bufSize = 1024
|
||||
)
|
||||
|
||||
var (
|
||||
runtimeContextPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &RuntimeContext{
|
||||
Buf: make([]byte, 0, bufSize),
|
||||
Ptrs: make([]uintptr, 128),
|
||||
KeepRefs: make([]unsafe.Pointer, 0, 8),
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type RuntimeContext struct {
|
||||
Buf []byte
|
||||
MarshalBuf []byte
|
||||
Ptrs []uintptr
|
||||
KeepRefs []unsafe.Pointer
|
||||
SeenPtr []uintptr
|
||||
BaseIndent int
|
||||
Prefix []byte
|
||||
IndentStr []byte
|
||||
}
|
||||
|
||||
func (c *RuntimeContext) Init(p uintptr, codelen int) {
|
||||
if len(c.Ptrs) < codelen {
|
||||
c.Ptrs = make([]uintptr, codelen)
|
||||
}
|
||||
c.Ptrs[0] = p
|
||||
c.KeepRefs = c.KeepRefs[:0]
|
||||
c.SeenPtr = c.SeenPtr[:0]
|
||||
c.BaseIndent = 0
|
||||
}
|
||||
|
||||
func (c *RuntimeContext) Ptr() uintptr {
|
||||
header := (*runtime.SliceHeader)(unsafe.Pointer(&c.Ptrs))
|
||||
return uintptr(header.Data)
|
||||
}
|
||||
|
||||
func TakeRuntimeContext() *RuntimeContext {
|
||||
return runtimeContextPool.Get().(*RuntimeContext)
|
||||
}
|
||||
|
||||
func ReleaseRuntimeContext(ctx *RuntimeContext) {
|
||||
runtimeContextPool.Put(ctx)
|
||||
}
|
||||
|
|
|
@ -304,32 +304,6 @@ func MapIterNext(it unsafe.Pointer)
|
|||
//go:noescape
|
||||
func MapLen(m unsafe.Pointer) int
|
||||
|
||||
type RuntimeContext struct {
|
||||
Buf []byte
|
||||
MarshalBuf []byte
|
||||
Ptrs []uintptr
|
||||
KeepRefs []unsafe.Pointer
|
||||
SeenPtr []uintptr
|
||||
BaseIndent int
|
||||
Prefix []byte
|
||||
IndentStr []byte
|
||||
}
|
||||
|
||||
func (c *RuntimeContext) Init(p uintptr, codelen int) {
|
||||
if len(c.Ptrs) < codelen {
|
||||
c.Ptrs = make([]uintptr, codelen)
|
||||
}
|
||||
c.Ptrs[0] = p
|
||||
c.KeepRefs = c.KeepRefs[:0]
|
||||
c.SeenPtr = c.SeenPtr[:0]
|
||||
c.BaseIndent = 0
|
||||
}
|
||||
|
||||
func (c *RuntimeContext) Ptr() uintptr {
|
||||
header := (*runtime.SliceHeader)(unsafe.Pointer(&c.Ptrs))
|
||||
return uintptr(header.Data)
|
||||
}
|
||||
|
||||
func AppendByteSlice(b []byte, src []byte) []byte {
|
||||
if src == nil {
|
||||
return append(b, `null`...)
|
||||
|
@ -551,13 +525,20 @@ func AppendStructEnd(b []byte) []byte {
|
|||
func AppendStructEndIndent(ctx *RuntimeContext, b []byte, indent int) []byte {
|
||||
b = append(b, '\n')
|
||||
b = append(b, ctx.Prefix...)
|
||||
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
|
||||
indentNum := ctx.BaseIndent + indent
|
||||
for i := 0; i < indentNum; i++ {
|
||||
b = append(b, ctx.IndentStr...)
|
||||
}
|
||||
return append(b, '}', ',', '\n')
|
||||
}
|
||||
|
||||
func AppendIndent(ctx *RuntimeContext, b []byte, indent int) []byte {
|
||||
b = append(b, ctx.Prefix...)
|
||||
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
|
||||
indentNum := ctx.BaseIndent + indent
|
||||
for i := 0; i < indentNum; i++ {
|
||||
b = append(b, ctx.IndentStr...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func IsNilForMarshaler(v interface{}) bool {
|
||||
|
|
|
@ -7,22 +7,44 @@ import (
|
|||
"github.com/goccy/go-json/internal/errors"
|
||||
)
|
||||
|
||||
func takeIndentSrcRuntimeContext(src []byte) (*RuntimeContext, []byte) {
|
||||
ctx := TakeRuntimeContext()
|
||||
buf := ctx.Buf[:0]
|
||||
buf = append(append(buf, src...), nul)
|
||||
ctx.Buf = buf
|
||||
return ctx, buf
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
srcCtx, srcBuf := takeIndentSrcRuntimeContext(src)
|
||||
dstCtx := TakeRuntimeContext()
|
||||
dst := dstCtx.Buf[:0]
|
||||
|
||||
dst, err := indentAndWrite(buf, dst, srcBuf, prefix, indentStr)
|
||||
if err != nil {
|
||||
ReleaseRuntimeContext(srcCtx)
|
||||
ReleaseRuntimeContext(dstCtx)
|
||||
return err
|
||||
}
|
||||
dstCtx.Buf = dst
|
||||
ReleaseRuntimeContext(srcCtx)
|
||||
ReleaseRuntimeContext(dstCtx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func indentAndWrite(buf *bytes.Buffer, dst []byte, src []byte, prefix, indentStr string) ([]byte, error) {
|
||||
dst, err := doIndent(dst, src, prefix, indentStr, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := buf.Write(dst); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return nil
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func doIndent(dst, src []byte, prefix, indentStr string, escape bool) ([]byte, error) {
|
||||
|
@ -94,7 +116,10 @@ func indentObject(
|
|||
indentNum++
|
||||
var err error
|
||||
for {
|
||||
dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum)...)
|
||||
dst = append(append(dst, '\n'), prefix...)
|
||||
for i := 0; i < indentNum; i++ {
|
||||
dst = append(dst, indentBytes...)
|
||||
}
|
||||
cursor = skipWhiteSpace(src, cursor)
|
||||
dst, cursor, err = compactString(dst, src, cursor, escape)
|
||||
if err != nil {
|
||||
|
@ -115,7 +140,10 @@ func indentObject(
|
|||
cursor = skipWhiteSpace(src, cursor)
|
||||
switch src[cursor] {
|
||||
case '}':
|
||||
dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum-1)...)
|
||||
dst = append(append(dst, '\n'), prefix...)
|
||||
for i := 0; i < indentNum-1; i++ {
|
||||
dst = append(dst, indentBytes...)
|
||||
}
|
||||
dst = append(dst, '}')
|
||||
cursor++
|
||||
return dst, cursor, nil
|
||||
|
@ -152,7 +180,10 @@ func indentArray(
|
|||
indentNum++
|
||||
var err error
|
||||
for {
|
||||
dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum)...)
|
||||
dst = append(append(dst, '\n'), prefix...)
|
||||
for i := 0; i < indentNum; i++ {
|
||||
dst = append(dst, indentBytes...)
|
||||
}
|
||||
dst, cursor, err = indentValue(dst, src, indentNum, cursor, prefix, indentBytes, escape)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
@ -160,7 +191,10 @@ func indentArray(
|
|||
cursor = skipWhiteSpace(src, cursor)
|
||||
switch src[cursor] {
|
||||
case ']':
|
||||
dst = append(append(append(dst, '\n'), prefix...), bytes.Repeat(indentBytes, indentNum-1)...)
|
||||
dst = append(append(dst, '\n'), prefix...)
|
||||
for i := 0; i < indentNum-1; i++ {
|
||||
dst = append(dst, indentBytes...)
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
cursor++
|
||||
return dst, cursor, nil
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package vm_escaped_indent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"unsafe"
|
||||
|
||||
|
@ -76,15 +75,3 @@ func appendNull(b []byte) []byte {
|
|||
func appendComma(b []byte) []byte {
|
||||
return append(b, ',', '\n')
|
||||
}
|
||||
|
||||
func appendStructEnd(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
|
||||
b = append(b, '\n')
|
||||
b = append(b, ctx.Prefix...)
|
||||
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
|
||||
return append(b, '}', ',', '\n')
|
||||
}
|
||||
|
||||
func appendIndent(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
|
||||
b = append(b, ctx.Prefix...)
|
||||
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package vm_escaped_indent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
@ -29,6 +28,8 @@ var (
|
|||
appendNumber = encoder.AppendNumber
|
||||
appendMarshalJSON = encoder.AppendMarshalJSONIndent
|
||||
appendMarshalText = encoder.AppendMarshalTextIndent
|
||||
appendStructEnd = encoder.AppendStructEndIndent
|
||||
appendIndent = encoder.AppendIndent
|
||||
errUnsupportedValue = encoder.ErrUnsupportedValue
|
||||
errUnsupportedFloat = encoder.ErrUnsupportedFloat
|
||||
mapiterinit = encoder.MapIterInit
|
||||
|
@ -518,8 +519,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
|
|||
sort.Sort(mapCtx.Slice)
|
||||
buf := mapCtx.Buf
|
||||
for _, item := range mapCtx.Slice.Items {
|
||||
buf = append(buf, ctx.Prefix...)
|
||||
buf = append(buf, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+code.Indent+1)...)
|
||||
buf = appendIndent(ctx, buf, code.Indent+1)
|
||||
buf = append(buf, item.Key...)
|
||||
buf[len(buf)-2] = ':'
|
||||
buf[len(buf)-1] = ' '
|
||||
|
@ -527,8 +527,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
|
|||
}
|
||||
buf = buf[:len(buf)-2]
|
||||
buf = append(buf, '\n')
|
||||
buf = append(buf, ctx.Prefix...)
|
||||
buf = append(buf, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+code.Indent)...)
|
||||
buf = appendIndent(ctx, buf, code.Indent)
|
||||
buf = append(buf, '}', ',', '\n')
|
||||
|
||||
b = b[:pos[0]]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package vm_indent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"unsafe"
|
||||
|
||||
|
@ -76,15 +75,3 @@ func appendNull(b []byte) []byte {
|
|||
func appendComma(b []byte) []byte {
|
||||
return append(b, ',', '\n')
|
||||
}
|
||||
|
||||
func appendStructEnd(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
|
||||
b = append(b, '\n')
|
||||
b = append(b, ctx.Prefix...)
|
||||
b = append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
|
||||
return append(b, '}', ',', '\n')
|
||||
}
|
||||
|
||||
func appendIndent(ctx *encoder.RuntimeContext, b []byte, indent int) []byte {
|
||||
b = append(b, ctx.Prefix...)
|
||||
return append(b, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+indent)...)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package vm_indent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
@ -29,6 +28,8 @@ var (
|
|||
appendNumber = encoder.AppendNumber
|
||||
appendMarshalJSON = encoder.AppendMarshalJSONIndent
|
||||
appendMarshalText = encoder.AppendMarshalTextIndent
|
||||
appendStructEnd = encoder.AppendStructEndIndent
|
||||
appendIndent = encoder.AppendIndent
|
||||
errUnsupportedValue = encoder.ErrUnsupportedValue
|
||||
errUnsupportedFloat = encoder.ErrUnsupportedFloat
|
||||
mapiterinit = encoder.MapIterInit
|
||||
|
@ -518,8 +519,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
|
|||
sort.Sort(mapCtx.Slice)
|
||||
buf := mapCtx.Buf
|
||||
for _, item := range mapCtx.Slice.Items {
|
||||
buf = append(buf, ctx.Prefix...)
|
||||
buf = append(buf, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+code.Indent+1)...)
|
||||
buf = appendIndent(ctx, buf, code.Indent+1)
|
||||
buf = append(buf, item.Key...)
|
||||
buf[len(buf)-2] = ':'
|
||||
buf[len(buf)-1] = ' '
|
||||
|
@ -527,8 +527,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt
|
|||
}
|
||||
buf = buf[:len(buf)-2]
|
||||
buf = append(buf, '\n')
|
||||
buf = append(buf, ctx.Prefix...)
|
||||
buf = append(buf, bytes.Repeat(ctx.IndentStr, ctx.BaseIndent+code.Indent)...)
|
||||
buf = appendIndent(ctx, buf, code.Indent)
|
||||
buf = append(buf, '}', ',', '\n')
|
||||
|
||||
b = b[:pos[0]]
|
||||
|
|
Loading…
Reference in New Issue