go-json/encode.go

283 lines
6.1 KiB
Go
Raw Permalink 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-04-21 08:19:50 +03:00
"io"
2020-04-19 13:51:22 +03:00
"reflect"
"strconv"
"sync"
"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-08-12 12:42:29 +03:00
w io.Writer
buf []byte
enabledIndent bool
enabledHTMLEscape bool
prefix []byte
indentStr []byte
indent int
structTypeToCompiledCode map[uintptr]*compiledCode
structTypeToCompiledIndentCode map[uintptr]*compiledCode
}
type compiledCode struct {
code *opcode
2020-04-19 13:51:22 +03:00
}
const (
bufSize = 1024
)
2020-04-30 07:52:24 +03:00
type opcodeMap struct {
2020-04-28 12:25:51 +03:00
sync.Map
}
2020-05-02 17:35:41 +03:00
type opcodeSet struct {
2020-08-09 11:48:28 +03:00
codeIndent sync.Pool
code sync.Pool
2020-05-02 17:35:41 +03:00
}
2020-05-03 16:19:55 +03:00
func (m *opcodeMap) get(k uintptr) *opcodeSet {
2020-04-28 12:25:51 +03:00
if v, ok := m.Load(k); ok {
2020-05-02 17:35:41 +03:00
return v.(*opcodeSet)
2020-04-28 12:25:51 +03:00
}
return nil
}
2020-05-03 16:19:55 +03:00
func (m *opcodeMap) set(k uintptr, op *opcodeSet) {
2020-04-28 12:25:51 +03:00
m.Store(k, op)
}
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-05-04 12:39:17 +03:00
cachedOpcode opcodeMap
marshalJSONType reflect.Type
marshalTextType reflect.Type
2020-04-19 13:51:22 +03:00
)
func init() {
encPool = sync.Pool{
New: func() interface{} {
return &Encoder{
2020-08-12 12:42:29 +03:00
buf: make([]byte, 0, bufSize),
structTypeToCompiledCode: map[uintptr]*compiledCode{},
structTypeToCompiledIndentCode: map[uintptr]*compiledCode{},
2020-04-19 13:51:22 +03:00
}
},
}
2020-04-30 07:52:24 +03:00
cachedOpcode = opcodeMap{}
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 {
2020-04-24 14:23:26 +03:00
if err := e.encode(v); err != nil {
2020-04-21 08:19:50 +03:00
return err
}
if _, err := e.w.Write(e.buf); err != nil {
return err
}
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-04-19 13:51:22 +03:00
e.buf = e.buf[:0]
2020-05-03 11:41:33 +03:00
e.indent = 0
e.enabledHTMLEscape = true
e.enabledIndent = false
2020-04-19 13:51:22 +03:00
}
2020-05-02 17:35:41 +03:00
func (e *Encoder) encodeForMarshal(v interface{}) ([]byte, error) {
if err := e.encode(v); err != nil {
return nil, err
}
if e.enabledIndent {
last := len(e.buf) - 1
if e.buf[last] == '\n' {
last--
}
length := last + 1
copied := make([]byte, length)
copy(copied, e.buf[0:length])
return copied, nil
}
copied := make([]byte, len(e.buf))
copy(copied, e.buf)
return copied, nil
}
func (e *Encoder) encode(v interface{}) error {
header := (*interfaceHeader)(unsafe.Pointer(&v))
typ := header.typ
2020-05-04 12:39:17 +03:00
2020-05-03 16:19:55 +03:00
typeptr := uintptr(unsafe.Pointer(typ))
if codeSet := cachedOpcode.get(typeptr); codeSet != nil {
2020-05-02 17:35:41 +03:00
var code *opcode
if e.enabledIndent {
2020-08-09 11:48:28 +03:00
code = codeSet.codeIndent.Get().(*opcode)
2020-05-02 17:35:41 +03:00
} else {
2020-08-09 11:48:28 +03:00
code = codeSet.code.Get().(*opcode)
2020-05-02 17:35:41 +03:00
}
p := uintptr(header.ptr)
code.ptr = p
if err := e.run(code); err != nil {
return err
}
2020-08-09 11:48:28 +03:00
if e.enabledIndent {
codeSet.codeIndent.Put(code)
} else {
codeSet.code.Put(code)
}
2020-05-02 17:35:41 +03:00
return nil
}
2020-05-03 16:19:55 +03:00
2020-05-08 17:59:49 +03:00
// noescape trick for header.typ ( reflect.*rtype )
2020-05-03 16:19:55 +03:00
copiedType := (*rtype)(unsafe.Pointer(typeptr))
2020-05-04 12:39:17 +03:00
codeIndent, err := e.compileHead(copiedType, true)
2020-05-02 17:35:41 +03:00
if err != nil {
return err
}
2020-05-04 12:39:17 +03:00
code, err := e.compileHead(copiedType, false)
2020-05-02 17:35:41 +03:00
if err != nil {
return err
}
2020-08-09 11:48:28 +03:00
codeSet := &opcodeSet{
codeIndent: sync.Pool{
New: func() interface{} {
return copyOpcode(codeIndent)
},
},
code: sync.Pool{
New: func() interface{} {
return copyOpcode(code)
},
},
}
2020-05-03 16:19:55 +03:00
cachedOpcode.set(typeptr, codeSet)
2020-05-02 17:35:41 +03:00
p := uintptr(header.ptr)
if e.enabledIndent {
2020-08-09 11:48:28 +03:00
codeIndent.ptr = p
2020-05-02 17:35:41 +03:00
return e.run(codeIndent)
}
2020-08-09 11:48:28 +03:00
code.ptr = p
2020-05-02 17:35:41 +03:00
return e.run(code)
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeInt(v int) {
e.encodeInt64(int64(v))
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeInt8(v int8) {
e.encodeInt64(int64(v))
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeInt16(v int16) {
e.encodeInt64(int64(v))
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeInt32(v int32) {
e.encodeInt64(int64(v))
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeInt64(v int64) {
2020-04-19 13:51:22 +03:00
e.buf = strconv.AppendInt(e.buf, v, 10)
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeUint(v uint) {
e.encodeUint64(uint64(v))
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeUint8(v uint8) {
e.encodeUint64(uint64(v))
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeUint16(v uint16) {
e.encodeUint64(uint64(v))
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeUint32(v uint32) {
e.encodeUint64(uint64(v))
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeUint64(v uint64) {
2020-04-19 13:51:22 +03:00
e.buf = strconv.AppendUint(e.buf, v, 10)
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeFloat32(v float32) {
2020-04-19 13:51:22 +03:00
e.buf = strconv.AppendFloat(e.buf, float64(v), 'f', -1, 32)
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeFloat64(v float64) {
2020-04-19 13:51:22 +03:00
e.buf = strconv.AppendFloat(e.buf, v, 'f', -1, 64)
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeBool(v bool) {
2020-04-19 13:51:22 +03:00
e.buf = strconv.AppendBool(e.buf, v)
}
2020-04-30 05:56:56 +03:00
func (e *Encoder) encodeBytes(b []byte) {
e.buf = append(e.buf, b...)
}
2020-05-03 11:41:33 +03:00
func (e *Encoder) encodeNull() {
e.buf = append(e.buf, 'n', 'u', 'l', 'l')
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeString(s string) {
2020-05-03 11:41:33 +03:00
if e.enabledHTMLEscape {
e.encodeEscapedString(s)
} else {
e.encodeNoEscapedString(s)
}
2020-04-19 13:51:22 +03:00
}
2020-04-21 08:19:50 +03:00
func (e *Encoder) encodeByte(b byte) {
2020-04-19 13:51:22 +03:00
e.buf = append(e.buf, b)
}
2020-05-02 17:35:41 +03:00
func (e *Encoder) encodeIndent(indent int) {
e.buf = append(e.buf, e.prefix...)
e.buf = append(e.buf, bytes.Repeat(e.indentStr, indent)...)
2020-04-28 12:25:51 +03:00
}