go-json/encode.go

345 lines
7.7 KiB
Go
Raw Normal View History

2020-04-19 13:51:22 +03:00
package json
import (
2020-05-02 17:35:41 +03:00
"bytes"
2020-05-08 14:22:57 +03:00
"encoding"
2020-09-15 17:22:35 +03:00
"encoding/base64"
2020-04-21 08:19:50 +03:00
"io"
"math"
2020-04-19 13:51:22 +03:00
"reflect"
"strconv"
"sync"
2020-12-15 08:46:10 +03:00
"sync/atomic"
2020-04-19 13:51:22 +03:00
"unsafe"
)
2020-04-21 08:19:50 +03:00
// An Encoder writes JSON values to an output stream.
2020-04-19 13:51:22 +03:00
type Encoder struct {
2020-12-24 21:53:48 +03:00
w io.Writer
ctx *encodeRuntimeContext
ptr unsafe.Pointer
buf []byte
enabledIndent bool
enabledHTMLEscape bool
unorderedMap bool
prefix []byte
indentStr []byte
2020-08-12 12:42:29 +03:00
}
type compiledCode struct {
code *opcode
2020-04-19 13:51:22 +03:00
}
const (
bufSize = 1024
)
2020-12-25 16:26:59 +03:00
const (
opCodeEscapedType = iota
opCodeEscapedIndentType
opCodeNoEscapeType
opCodeNoEscapeIndentType
)
2020-05-02 17:35:41 +03:00
type opcodeSet struct {
2020-12-25 16:26:59 +03:00
escapedCode *opcode
escapedCodeIndent *opcode
code *opcode
codeIndent *opcode
codeLength int
2020-05-02 17:35:41 +03:00
}
2020-12-15 08:46:10 +03:00
func loadOpcodeMap() map[uintptr]*opcodeSet {
p := atomic.LoadPointer(&cachedOpcode)
return *(*map[uintptr]*opcodeSet)(unsafe.Pointer(&p))
2020-04-28 12:25:51 +03:00
}
2020-12-15 08:46:10 +03:00
func storeOpcodeSet(typ uintptr, set *opcodeSet, m map[uintptr]*opcodeSet) {
newOpcodeMap := make(map[uintptr]*opcodeSet, len(m)+1)
newOpcodeMap[typ] = set
for k, v := range m {
newOpcodeMap[k] = v
}
atomic.StorePointer(&cachedOpcode, *(*unsafe.Pointer)(unsafe.Pointer(&newOpcodeMap)))
2020-04-28 12:25:51 +03:00
}
2020-04-19 13:51:22 +03:00
var (
2020-05-04 12:39:17 +03:00
encPool sync.Pool
2020-08-09 11:48:28 +03:00
codePool sync.Pool
2020-12-15 08:46:10 +03:00
cachedOpcode unsafe.Pointer // map[uintptr]*opcodeSet
2020-05-04 12:39:17 +03:00
marshalJSONType reflect.Type
marshalTextType reflect.Type
2020-04-19 13:51:22 +03:00
)
func init() {
encPool = sync.Pool{
New: func() interface{} {
return &Encoder{
ctx: &encodeRuntimeContext{
ptrs: make([]uintptr, 128),
keepRefs: make([]unsafe.Pointer, 0, 8),
},
2020-12-24 21:53:48 +03:00
buf: make([]byte, 0, bufSize),
2020-04-19 13:51:22 +03:00
}
},
}
2020-05-04 12:39:17 +03:00
marshalJSONType = reflect.TypeOf((*Marshaler)(nil)).Elem()
2020-05-08 14:22:57 +03:00
marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
2020-04-19 13:51:22 +03:00
enc := encPool.Get().(*Encoder)
2020-04-21 08:19:50 +03:00
enc.w = w
enc.reset()
2020-04-19 13:51:22 +03:00
return enc
}
2020-04-21 08:19:50 +03:00
// Encode writes the JSON encoding of v to the stream, followed by a newline character.
//
// See the documentation for Marshal for details about the conversion of Go values to JSON.
func (e *Encoder) Encode(v interface{}) error {
return e.EncodeWithOption(v)
}
// EncodeWithOption call Encode with EncodeOption.
func (e *Encoder) EncodeWithOption(v interface{}, opts ...EncodeOption) error {
for _, opt := range opts {
if err := opt(e); err != nil {
return err
}
}
2020-12-30 13:32:38 +03:00
header := (*interfaceHeader)(unsafe.Pointer(&v))
buf, err := e.encode(header)
2020-12-24 21:53:48 +03:00
if err != nil {
2020-04-21 08:19:50 +03:00
return err
}
if e.enabledIndent {
2020-12-24 21:53:48 +03:00
buf = buf[:len(buf)-2]
} else {
2020-12-24 21:53:48 +03:00
buf = buf[:len(buf)-1]
}
2020-12-24 21:53:48 +03:00
buf = append(buf, '\n')
if _, err := e.w.Write(buf); err != nil {
2020-04-21 08:19:50 +03:00
return err
}
2020-12-24 21:53:48 +03:00
e.buf = buf[:0]
2020-04-21 08:19:50 +03:00
return nil
}
// SetEscapeHTML specifies whether problematic HTML characters should be escaped inside JSON quoted strings.
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e to avoid certain safety problems that can arise when embedding JSON in HTML.
//
// In non-HTML settings where the escaping interferes with the readability of the output, SetEscapeHTML(false) disables this behavior.
func (e *Encoder) SetEscapeHTML(on bool) {
2020-05-03 11:41:33 +03:00
e.enabledHTMLEscape = on
2020-04-21 08:19:50 +03:00
}
// SetIndent instructs the encoder to format each subsequent encoded value as if indented by the package-level function Indent(dst, src, prefix, indent).
// Calling SetIndent("", "") disables indentation.
func (e *Encoder) SetIndent(prefix, indent string) {
2020-05-02 17:35:41 +03:00
if prefix == "" && indent == "" {
e.enabledIndent = false
return
}
e.prefix = []byte(prefix)
e.indentStr = []byte(indent)
e.enabledIndent = true
2020-04-21 08:19:50 +03:00
}
func (e *Encoder) release() {
e.w = nil
encPool.Put(e)
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) reset() {
2020-05-03 11:41:33 +03:00
e.enabledHTMLEscape = true
e.enabledIndent = false
e.unorderedMap = false
2020-04-19 13:51:22 +03:00
}
2020-12-30 13:32:38 +03:00
func (e *Encoder) encodeForMarshal(header *interfaceHeader) ([]byte, error) {
buf, err := e.encode(header)
2020-12-24 21:53:48 +03:00
if err != nil {
2020-05-02 17:35:41 +03:00
return nil, err
}
2020-12-24 21:53:48 +03:00
e.buf = buf
2020-05-02 17:35:41 +03:00
if e.enabledIndent {
2020-12-24 21:53:48 +03:00
copied := make([]byte, len(buf)-2)
copy(copied, buf)
2020-05-02 17:35:41 +03:00
return copied, nil
}
2020-12-24 21:53:48 +03:00
copied := make([]byte, len(buf)-1)
copy(copied, buf)
2020-05-02 17:35:41 +03:00
return copied, nil
}
2020-12-30 13:32:38 +03:00
func (e *Encoder) encode(header *interfaceHeader) ([]byte, error) {
2020-12-24 21:53:48 +03:00
b := e.buf[:0]
2020-12-30 13:32:38 +03:00
if header.ptr == nil {
b = encodeNull(b)
if e.enabledIndent {
b = encodeIndentComma(b)
} else {
b = encodeComma(b)
}
return b, nil
2020-08-21 05:07:55 +03:00
}
2020-05-02 17:35:41 +03:00
typ := header.typ
2020-05-04 12:39:17 +03:00
2020-05-03 16:19:55 +03:00
typeptr := uintptr(unsafe.Pointer(typ))
2020-12-15 08:46:10 +03:00
opcodeMap := loadOpcodeMap()
if codeSet, exists := opcodeMap[typeptr]; exists {
2020-05-02 17:35:41 +03:00
var code *opcode
if e.enabledIndent {
2020-12-25 16:26:59 +03:00
if e.enabledHTMLEscape {
code = codeSet.escapedCodeIndent
} else {
code = codeSet.codeIndent
}
2020-05-02 17:35:41 +03:00
} else {
2020-12-25 16:26:59 +03:00
if e.enabledHTMLEscape {
code = codeSet.escapedCode
} else {
code = codeSet.code
}
2020-05-02 17:35:41 +03:00
}
ctx := e.ctx
2020-05-02 17:35:41 +03:00
p := uintptr(header.ptr)
ctx.init(p, codeSet.codeLength)
return e.run(ctx, b, code)
2020-05-02 17:35:41 +03:00
}
2020-05-03 16:19:55 +03:00
2020-05-08 17:59:49 +03:00
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**rtype)(unsafe.Pointer(&typeptr))
2020-05-03 16:19:55 +03:00
2020-08-29 09:11:31 +03:00
code, err := e.compileHead(&encodeCompileContext{
2020-12-24 21:53:48 +03:00
typ: copiedType,
root: true,
structTypeToCompiledCode: map[uintptr]*compiledCode{},
2020-08-29 09:11:31 +03:00
})
2020-05-02 17:35:41 +03:00
if err != nil {
return nil, err
2020-05-02 17:35:41 +03:00
}
2020-09-15 14:47:41 +03:00
code = copyOpcode(code)
2020-12-25 11:03:56 +03:00
codeIndent := toIndent(code)
2020-08-31 15:59:22 +03:00
codeLength := code.totalLength()
2020-08-09 11:48:28 +03:00
codeSet := &opcodeSet{
2020-12-25 16:26:59 +03:00
escapedCode: toEscaped(code),
escapedCodeIndent: toEscaped(codeIndent),
code: code,
codeIndent: codeIndent,
codeLength: codeLength,
2020-08-09 11:48:28 +03:00
}
2020-12-15 08:46:10 +03:00
storeOpcodeSet(typeptr, codeSet, opcodeMap)
2020-05-02 17:35:41 +03:00
p := uintptr(header.ptr)
ctx := e.ctx
ctx.init(p, codeLength)
2020-08-30 21:14:37 +03:00
var c *opcode
2020-05-02 17:35:41 +03:00
if e.enabledIndent {
2020-12-25 16:26:59 +03:00
if e.enabledHTMLEscape {
c = codeSet.escapedCodeIndent
} else {
c = codeSet.codeIndent
}
2020-08-30 21:14:37 +03:00
} else {
2020-12-25 16:26:59 +03:00
if e.enabledHTMLEscape {
c = codeSet.escapedCode
} else {
c = codeSet.code
}
}
2020-08-30 21:14:37 +03:00
b, err = e.run(ctx, b, c)
if err != nil {
return nil, err
2020-05-02 17:35:41 +03:00
}
return b, nil
2020-05-02 17:35:41 +03:00
}
func encodeFloat32(b []byte, v float32) []byte {
f64 := float64(v)
abs := math.Abs(f64)
fmt := byte('f')
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
if abs != 0 {
f32 := float32(abs)
if f32 < 1e-6 || f32 >= 1e21 {
fmt = 'e'
}
}
return strconv.AppendFloat(b, f64, fmt, -1, 32)
2020-04-19 13:51:22 +03:00
}
func encodeFloat64(b []byte, v float64) []byte {
abs := math.Abs(v)
fmt := byte('f')
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
if abs != 0 {
if abs < 1e-6 || abs >= 1e21 {
fmt = 'e'
}
}
return strconv.AppendFloat(b, v, fmt, -1, 64)
2020-04-19 13:51:22 +03:00
}
func encodeBool(b []byte, v bool) []byte {
if v {
return append(b, "true"...)
}
return append(b, "false"...)
}
func encodeBytes(dst []byte, src []byte) []byte {
return append(dst, src...)
}
func encodeNull(b []byte) []byte {
return append(b, "null"...)
2020-04-19 13:51:22 +03:00
}
func encodeComma(b []byte) []byte {
return append(b, ',')
2020-04-30 05:56:56 +03:00
}
func encodeIndentComma(b []byte) []byte {
return append(b, ',', '\n')
2020-05-03 11:41:33 +03:00
}
func appendStructEnd(b []byte) []byte {
return append(b, '}', ',')
}
func (e *Encoder) appendStructEndIndent(b []byte, indent int) []byte {
b = append(b, '\n')
b = append(b, e.prefix...)
b = append(b, bytes.Repeat(e.indentStr, indent)...)
return append(b, '}', ',', '\n')
}
func encodeByteSlice(b []byte, src []byte) []byte {
encodedLen := base64.StdEncoding.EncodedLen(len(src))
b = append(b, '"')
pos := len(b)
remainLen := cap(b[pos:])
2020-09-15 17:22:35 +03:00
var buf []byte
if remainLen > encodedLen {
buf = b[pos : pos+encodedLen]
2020-09-15 17:22:35 +03:00
} else {
buf = make([]byte, encodedLen)
}
base64.StdEncoding.Encode(buf, src)
return append(append(b, buf...), '"')
2020-04-19 13:51:22 +03:00
}
func (e *Encoder) encodeIndent(b []byte, indent int) []byte {
b = append(b, e.prefix...)
return append(b, bytes.Repeat(e.indentStr, indent)...)
2020-04-28 12:25:51 +03:00
}