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"
2020-08-20 09:36:00 +03:00
"math"
2021-03-03 08:54:40 +03:00
"reflect"
2020-04-19 13:51:22 +03:00
"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-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 {
2020-09-16 12:15:47 +03:00
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-09-16 12:15:47 +03:00
}
2020-12-24 21:53:48 +03:00
if err != nil {
2020-04-21 08:19:50 +03:00
return err
}
2020-11-16 13:10:46 +03:00
if e . enabledIndent {
2020-12-24 21:53:48 +03:00
buf = buf [ : len ( buf ) - 2 ]
2020-11-16 13:10:46 +03:00
} else {
2020-12-24 21:53:48 +03:00
buf = buf [ : len ( buf ) - 1 ]
2020-11-16 13:10:46 +03:00
}
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 {
2020-12-19 16:40:03 +03:00
b = encodeNull ( b )
2021-01-31 16:45:59 +03:00
b = encodeComma ( b )
2020-12-19 16:40:03 +03:00
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-30 19:48:54 +03:00
}
2021-01-31 16:45:59 +03:00
header := ( * interfaceHeader ) ( unsafe . Pointer ( & v ) )
typ := header . typ
2021-01-30 19:48:54 +03:00
2021-01-31 16:45:59 +03:00
typeptr := uintptr ( unsafe . Pointer ( typ ) )
codeSet , err := encodeCompileToGetCodeSet ( typeptr )
2021-01-30 19:48:54 +03:00
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 {
2020-12-19 16:40:03 +03:00
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
}
2020-12-19 16:40:03 +03:00
func encodeFloat32 ( b [ ] byte , v float32 ) [ ] byte {
2020-08-20 09:36:00 +03:00
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'
}
}
2020-12-19 16:40:03 +03:00
return strconv . AppendFloat ( b , f64 , fmt , - 1 , 32 )
2020-04-19 13:51:22 +03:00
}
2020-12-19 16:40:03 +03:00
func encodeFloat64 ( b [ ] byte , v float64 ) [ ] byte {
2020-08-20 09:36:00 +03:00
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'
}
}
2020-12-19 16:40:03 +03:00
return strconv . AppendFloat ( b , v , fmt , - 1 , 64 )
2020-04-19 13:51:22 +03:00
}
2020-12-19 16:40:03 +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
}
2020-12-19 16:40:03 +03:00
func encodeComma ( b [ ] byte ) [ ] byte {
return append ( b , ',' )
2020-04-30 05:56:56 +03:00
}
2020-12-19 16:40:03 +03:00
func encodeIndentComma ( b [ ] byte ) [ ] byte {
return append ( b , ',' , '\n' )
2020-05-03 11:41:33 +03:00
}
2020-12-29 17:17:39 +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 {
2020-12-29 17:17:39 +03:00
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 ) ... )
2020-12-29 17:17:39 +03:00
return append ( b , '}' , ',' , '\n' )
}
2020-12-19 16:40:03 +03:00
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 {
2020-12-19 16:40:03 +03:00
buf = b [ pos : pos + encodedLen ]
2020-09-15 17:22:35 +03:00
} else {
buf = make ( [ ] byte , encodedLen )
}
2020-12-19 16:40:03 +03:00
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
}
func encodeMarshalText ( b [ ] byte , v interface { } ) ( [ ] byte , error ) {
rv := reflect . ValueOf ( v )
bytes , err := rv . Interface ( ) . ( encoding . TextMarshaler ) . MarshalText ( )
if err != nil {
return nil , & MarshalerError { Type : rv . Type ( ) , Err : err }
}
return encodeNoEscapedString ( b , * ( * string ) ( unsafe . Pointer ( & bytes ) ) ) , nil
}