go-json/encode.go

431 lines
11 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"
2021-03-03 08:54:40 +03:00
"encoding"
2020-09-15 17:22:35 +03:00
"encoding/base64"
2021-03-03 08:54:40 +03:00
"fmt"
2020-04-21 08:19:50 +03:00
"io"
"math"
2021-03-03 08:54:40 +03:00
"reflect"
2020-04-19 13:51:22 +03:00
"strconv"
2021-03-09 15:38:58 +03:00
"strings"
2020-04-19 13:51:22 +03:00
"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-12-24 21:53:48 +03:00
w io.Writer
enabledIndent bool
enabledHTMLEscape bool
2021-01-31 16:45:59 +03:00
prefix string
indentStr string
2020-04-19 13:51:22 +03:00
}
const (
2021-01-31 16:45:59 +03:00
bufSize = 1024
2020-04-19 13:51:22 +03:00
)
2021-01-31 16:45:59 +03:00
type EncodeOption int
2020-12-25 16:26:59 +03:00
const (
2021-01-31 16:45:59 +03:00
EncodeOptionHTMLEscape EncodeOption = 1 << iota
EncodeOptionIndent
EncodeOptionUnorderedMap
2020-12-25 16:26:59 +03:00
)
2020-04-19 13:51:22 +03:00
var (
2021-01-31 16:45:59 +03:00
encRuntimeContextPool = sync.Pool{
2020-04-19 13:51:22 +03:00
New: func() interface{} {
2021-01-31 16:45:59 +03:00
return &encodeRuntimeContext{
buf: make([]byte, 0, bufSize),
ptrs: make([]uintptr, 128),
keepRefs: make([]unsafe.Pointer, 0, 8),
2020-04-19 13:51:22 +03:00
}
},
}
2021-01-31 16:45:59 +03:00
)
func takeEncodeRuntimeContext() *encodeRuntimeContext {
return encRuntimeContextPool.Get().(*encodeRuntimeContext)
2020-04-19 13:51:22 +03:00
}
2021-01-31 16:45:59 +03:00
func releaseEncodeRuntimeContext(ctx *encodeRuntimeContext) {
encRuntimeContextPool.Put(ctx)
2020-04-19 13:51:22 +03:00
}
2021-01-31 16:45:59 +03:00
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w, enabledHTMLEscape: true}
2021-01-26 18:42:19 +03:00
}
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.
2021-01-31 16:45:59 +03:00
func (e *Encoder) EncodeWithOption(v interface{}, optFuncs ...EncodeOptionFunc) error {
2021-02-01 05:36:41 +03:00
ctx := takeEncodeRuntimeContext()
err := e.encodeWithOption(ctx, v, optFuncs...)
releaseEncodeRuntimeContext(ctx)
return err
}
func (e *Encoder) encodeWithOption(ctx *encodeRuntimeContext, v interface{}, optFuncs ...EncodeOptionFunc) error {
2021-01-31 16:45:59 +03:00
var opt EncodeOption
if e.enabledHTMLEscape {
opt |= EncodeOptionHTMLEscape
}
for _, optFunc := range optFuncs {
opt = optFunc(opt)
}
var (
buf []byte
err error
)
if e.enabledIndent {
2021-02-01 05:36:41 +03:00
buf, err = encodeIndent(ctx, v, e.prefix, e.indentStr, opt)
2021-01-31 16:45:59 +03:00
} else {
2021-02-01 05:36:41 +03:00
buf, err = encode(ctx, v, opt)
}
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
}
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
}
2021-01-31 16:45:59 +03:00
e.prefix = prefix
e.indentStr = indent
2020-05-02 17:35:41 +03:00
e.enabledIndent = true
2020-04-21 08:19:50 +03:00
}
2021-01-31 16:45:59 +03:00
func marshal(v interface{}, opt EncodeOption) ([]byte, error) {
2021-02-01 05:36:41 +03:00
ctx := takeEncodeRuntimeContext()
2021-02-01 16:31:39 +03:00
buf, err := encode(ctx, v, opt|EncodeOptionHTMLEscape)
2020-12-24 21:53:48 +03:00
if err != nil {
2021-02-01 05:36:41 +03:00
releaseEncodeRuntimeContext(ctx)
2020-05-02 17:35:41 +03:00
return nil, err
}
2020-12-24 21:53:48 +03:00
2021-01-26 18:42:19 +03:00
// this line exists to escape call of `runtime.makeslicecopy` .
// if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`,
// dst buffer size and src buffer size are differrent.
// in this case, compiler uses `runtime.makeslicecopy`, but it is slow.
buf = buf[:len(buf)-1]
copied := make([]byte, len(buf))
2020-12-24 21:53:48 +03:00
copy(copied, buf)
2021-02-01 05:36:41 +03:00
releaseEncodeRuntimeContext(ctx)
2020-05-02 17:35:41 +03:00
return copied, nil
}
2021-01-31 16:53:01 +03:00
func marshalNoEscape(v interface{}, opt EncodeOption) ([]byte, error) {
2021-02-01 05:36:41 +03:00
ctx := takeEncodeRuntimeContext()
2021-02-01 16:31:39 +03:00
buf, err := encodeNoEscape(ctx, v, opt|EncodeOptionHTMLEscape)
2021-01-31 16:53:01 +03:00
if err != nil {
2021-02-01 05:36:41 +03:00
releaseEncodeRuntimeContext(ctx)
2021-01-31 16:53:01 +03:00
return nil, err
}
// this line exists to escape call of `runtime.makeslicecopy` .
// if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`,
// dst buffer size and src buffer size are differrent.
// in this case, compiler uses `runtime.makeslicecopy`, but it is slow.
buf = buf[:len(buf)-1]
copied := make([]byte, len(buf))
copy(copied, buf)
2021-02-01 05:36:41 +03:00
releaseEncodeRuntimeContext(ctx)
2021-01-31 16:53:01 +03:00
return copied, nil
}
2021-01-31 16:45:59 +03:00
func marshalIndent(v interface{}, prefix, indent string, opt EncodeOption) ([]byte, error) {
2021-02-01 05:36:41 +03:00
ctx := takeEncodeRuntimeContext()
2021-02-01 16:31:39 +03:00
buf, err := encodeIndent(ctx, v, prefix, indent, opt|EncodeOptionHTMLEscape)
2021-01-31 16:45:59 +03:00
if err != nil {
2021-02-01 05:36:41 +03:00
releaseEncodeRuntimeContext(ctx)
2021-01-31 16:45:59 +03:00
return nil, err
}
2021-02-01 05:36:41 +03:00
buf = buf[:len(buf)-2]
2021-01-31 16:45:59 +03:00
copied := make([]byte, len(buf))
copy(copied, buf)
2021-02-01 05:36:41 +03:00
releaseEncodeRuntimeContext(ctx)
2021-01-31 16:45:59 +03:00
return copied, nil
}
2021-02-01 05:36:41 +03:00
func encode(ctx *encodeRuntimeContext, v interface{}, opt EncodeOption) ([]byte, error) {
2021-01-31 16:45:59 +03:00
b := ctx.buf[:0]
if v == nil {
b = encodeNull(b)
2021-01-31 16:45:59 +03:00
b = encodeComma(b)
return b, nil
2020-08-21 05:07:55 +03:00
}
2021-01-31 16:45:59 +03:00
header := (*interfaceHeader)(unsafe.Pointer(&v))
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))
2021-01-31 16:45:59 +03:00
codeSet, err := encodeCompileToGetCodeSet(typeptr)
2021-01-24 09:17:39 +03:00
if err != nil {
return nil, err
}
2021-01-10 23:16:37 +03:00
2021-01-24 09:17:39 +03:00
p := uintptr(header.ptr)
ctx.init(p, codeSet.codeLength)
2021-01-31 16:45:59 +03:00
buf, err := encodeRunCode(ctx, b, codeSet, opt)
ctx.keepRefs = append(ctx.keepRefs, header.ptr)
if err != nil {
return nil, err
2021-01-24 09:17:39 +03:00
}
2021-01-31 16:45:59 +03:00
ctx.buf = buf
return buf, nil
2021-01-24 09:17:39 +03:00
}
2021-02-01 05:36:41 +03:00
func encodeNoEscape(ctx *encodeRuntimeContext, v interface{}, opt EncodeOption) ([]byte, error) {
2021-01-31 16:53:01 +03:00
b := ctx.buf[:0]
if v == nil {
b = encodeNull(b)
b = encodeComma(b)
return b, nil
}
header := (*interfaceHeader)(unsafe.Pointer(&v))
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encodeCompileToGetCodeSet(typeptr)
if err != nil {
return nil, err
}
p := uintptr(header.ptr)
ctx.init(p, codeSet.codeLength)
buf, err := encodeRunCode(ctx, b, codeSet, opt)
if err != nil {
return nil, err
}
ctx.buf = buf
return buf, nil
}
2021-02-01 05:36:41 +03:00
func encodeIndent(ctx *encodeRuntimeContext, v interface{}, prefix, indent string, opt EncodeOption) ([]byte, error) {
2021-01-31 16:45:59 +03:00
b := ctx.buf[:0]
if v == nil {
b = encodeNull(b)
b = encodeIndentComma(b)
return b, nil
}
2021-01-31 16:45:59 +03:00
header := (*interfaceHeader)(unsafe.Pointer(&v))
typ := header.typ
2021-01-31 16:45:59 +03:00
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encodeCompileToGetCodeSet(typeptr)
if err != nil {
return nil, err
}
2021-01-31 16:45:59 +03:00
p := uintptr(header.ptr)
ctx.init(p, codeSet.codeLength)
buf, err := encodeRunIndentCode(ctx, b, codeSet, prefix, indent, opt)
2020-05-03 16:19:55 +03:00
2021-01-31 16:45:59 +03:00
ctx.keepRefs = append(ctx.keepRefs, header.ptr)
2020-05-03 16:19:55 +03:00
2020-05-02 17:35:41 +03:00
if err != nil {
return nil, err
2020-05-02 17:35:41 +03:00
}
2021-01-31 16:45:59 +03:00
ctx.buf = buf
return buf, nil
}
func encodeRunCode(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt EncodeOption) ([]byte, error) {
if (opt & EncodeOptionHTMLEscape) != 0 {
return encodeRunEscaped(ctx, b, codeSet, opt)
}
return encodeRun(ctx, b, codeSet, opt)
}
func encodeRunIndentCode(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, prefix, indent string, opt EncodeOption) ([]byte, error) {
ctx.prefix = []byte(prefix)
ctx.indentStr = []byte(indent)
if (opt & EncodeOptionHTMLEscape) != 0 {
return encodeRunEscapedIndent(ctx, b, codeSet, opt)
2020-08-09 11:48:28 +03:00
}
2021-01-31 16:45:59 +03:00
return encodeRunIndent(ctx, b, codeSet, opt)
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 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, '}', ',')
}
2021-01-31 16:45:59 +03:00
func appendStructEndIndent(ctx *encodeRuntimeContext, b []byte, indent int) []byte {
b = append(b, '\n')
2021-01-31 16:45:59 +03:00
b = append(b, ctx.prefix...)
b = append(b, bytes.Repeat(ctx.indentStr, ctx.baseIndent+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
}
2021-02-01 05:36:41 +03:00
func appendIndent(ctx *encodeRuntimeContext, b []byte, indent int) []byte {
2021-01-31 16:45:59 +03:00
b = append(b, ctx.prefix...)
return append(b, bytes.Repeat(ctx.indentStr, ctx.baseIndent+indent)...)
2020-04-28 12:25:51 +03:00
}
2021-03-03 08:54:40 +03:00
func encodeMarshalJSON(b []byte, v interface{}) ([]byte, error) {
2021-03-06 05:10:01 +03:00
bb, err := v.(Marshaler).MarshalJSON()
2021-03-03 08:54:40 +03:00
if err != nil {
2021-03-06 05:10:01 +03:00
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
2021-03-03 08:54:40 +03:00
}
if len(bb) == 0 {
return nil, errUnexpectedEndOfJSON(
2021-03-06 05:10:01 +03:00
fmt.Sprintf("error calling MarshalJSON for type %s", reflect.TypeOf(v)),
2021-03-03 08:54:40 +03:00
0,
)
}
buf := bytes.NewBuffer(b)
//TODO: we should validate buffer with `compact`
if err := compact(buf, bb, false); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
2021-03-09 15:38:58 +03:00
func encodeMarshalJSONIndent(ctx *encodeRuntimeContext, b []byte, v interface{}, indent int) ([]byte, error) {
bb, err := v.(Marshaler).MarshalJSON()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
if len(bb) == 0 {
return nil, errUnexpectedEndOfJSON(
fmt.Sprintf("error calling MarshalJSON for type %s", reflect.TypeOf(v)),
0,
)
}
var compactBuf bytes.Buffer
if err := compact(&compactBuf, bb, false); err != nil {
return nil, err
}
var indentBuf bytes.Buffer
if err := encodeWithIndent(
&indentBuf,
compactBuf.Bytes(),
string(ctx.prefix)+strings.Repeat(string(ctx.indentStr), ctx.baseIndent+indent),
string(ctx.indentStr),
); err != nil {
return nil, err
}
return append(b, indentBuf.Bytes()...), nil
}
2021-03-03 08:54:40 +03:00
func encodeMarshalText(b []byte, v interface{}) ([]byte, error) {
2021-03-06 06:51:09 +03:00
bytes, err := v.(encoding.TextMarshaler).MarshalText()
2021-03-03 08:54:40 +03:00
if err != nil {
2021-03-06 06:51:09 +03:00
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
2021-03-03 08:54:40 +03:00
}
return encodeNoEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}
2021-03-09 15:38:58 +03:00
func encodeMarshalTextIndent(b []byte, v interface{}) ([]byte, error) {
bytes, err := v.(encoding.TextMarshaler).MarshalText()
if err != nil {
return nil, &MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
return encodeNoEscapedString(b, *(*string)(unsafe.Pointer(&bytes))), nil
}