/* * Copyright 2021 ByteDance Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package encoder import ( `bytes` `encoding/json` `reflect` `runtime` `unsafe` `github.com/bytedance/sonic/internal/native` `github.com/bytedance/sonic/internal/native/types` `github.com/bytedance/sonic/internal/rt` `github.com/bytedance/sonic/utf8` `github.com/bytedance/sonic/option` ) // Options is a set of encoding options. type Options uint64 const ( bitSortMapKeys = iota bitEscapeHTML bitCompactMarshaler bitNoQuoteTextMarshaler bitNoNullSliceOrMap bitValidateString // used for recursive compile bitPointerValue = 63 ) const ( // SortMapKeys indicates that the keys of a map needs to be sorted // before serializing into JSON. // WARNING: This hurts performance A LOT, USE WITH CARE. SortMapKeys Options = 1 << bitSortMapKeys // EscapeHTML indicates encoder to escape all HTML characters // after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape). // WARNING: This hurts performance A LOT, USE WITH CARE. EscapeHTML Options = 1 << bitEscapeHTML // CompactMarshaler indicates that the output JSON from json.Marshaler // is always compact and needs no validation CompactMarshaler Options = 1 << bitCompactMarshaler // NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler // is always escaped string and needs no quoting NoQuoteTextMarshaler Options = 1 << bitNoQuoteTextMarshaler // NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}', // instead of 'null' NoNullSliceOrMap Options = 1 << bitNoNullSliceOrMap // ValidateString indicates that encoder should validate the input string // before encoding it into JSON. ValidateString Options = 1 << bitValidateString // CompatibleWithStd is used to be compatible with std encoder. CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler ) // Encoder represents a specific set of encoder configurations. type Encoder struct { Opts Options prefix string indent string } // Encode returns the JSON encoding of v. func (self *Encoder) Encode(v interface{}) ([]byte, error) { if self.indent != "" || self.prefix != "" { return EncodeIndented(v, self.prefix, self.indent, self.Opts) } return Encode(v, self.Opts) } // SortKeys enables the SortMapKeys option. func (self *Encoder) SortKeys() *Encoder { self.Opts |= SortMapKeys return self } // SetEscapeHTML specifies if option EscapeHTML opens func (self *Encoder) SetEscapeHTML(f bool) { if f { self.Opts |= EscapeHTML } else { self.Opts &= ^EscapeHTML } } // SetValidateString specifies if option ValidateString opens func (self *Encoder) SetValidateString(f bool) { if f { self.Opts |= ValidateString } else { self.Opts &= ^ValidateString } } // SetCompactMarshaler specifies if option CompactMarshaler opens func (self *Encoder) SetCompactMarshaler(f bool) { if f { self.Opts |= CompactMarshaler } else { self.Opts &= ^CompactMarshaler } } // SetNoQuoteTextMarshaler specifies if option NoQuoteTextMarshaler opens func (self *Encoder) SetNoQuoteTextMarshaler(f bool) { if f { self.Opts |= NoQuoteTextMarshaler } else { self.Opts &= ^NoQuoteTextMarshaler } } // SetIndent instructs the encoder to format each subsequent encoded // value as if indented by the package-level function EncodeIndent(). // Calling SetIndent("", "") disables indentation. func (enc *Encoder) SetIndent(prefix, indent string) { enc.prefix = prefix enc.indent = indent } // Quote returns the JSON-quoted version of s. func Quote(s string) string { var n int var p []byte /* check for empty string */ if s == "" { return `""` } /* allocate space for result */ n = len(s) + 2 p = make([]byte, 0, n) /* call the encoder */ _ = encodeString(&p, s) return rt.Mem2Str(p) } // Encode returns the JSON encoding of val, encoded with opts. func Encode(val interface{}, opts Options) ([]byte, error) { var ret []byte buf := newBytes() err := encodeInto(&buf, val, opts) /* check for errors */ if err != nil { freeBytes(buf) return nil, err } /* htmlescape or correct UTF-8 if opts enable */ old := buf buf = encodeFinish(old, opts) pbuf := ((*rt.GoSlice)(unsafe.Pointer(&buf))).Ptr pold := ((*rt.GoSlice)(unsafe.Pointer(&old))).Ptr /* return when allocated a new buffer */ if pbuf != pold { freeBytes(old) return buf, nil } /* make a copy of the result */ ret = make([]byte, len(buf)) copy(ret, buf) freeBytes(buf) /* return the buffer into pool */ return ret, nil } // EncodeInto is like Encode but uses a user-supplied buffer instead of allocating // a new one. func EncodeInto(buf *[]byte, val interface{}, opts Options) error { err := encodeInto(buf, val, opts) if err != nil { return err } *buf = encodeFinish(*buf, opts) return err } func encodeInto(buf *[]byte, val interface{}, opts Options) error { stk := newStack() efv := rt.UnpackEface(val) err := encodeTypedPointer(buf, efv.Type, &efv.Value, stk, uint64(opts)) /* return the stack into pool */ if err != nil { resetStack(stk) } freeStack(stk) /* avoid GC ahead */ runtime.KeepAlive(buf) runtime.KeepAlive(efv) return err } func encodeFinish(buf []byte, opts Options) []byte { if opts & EscapeHTML != 0 { buf = HTMLEscape(nil, buf) } if opts & ValidateString != 0 && !utf8.Validate(buf) { buf = utf8.CorrectWith(nil, buf, `\ufffd`) } return buf } var typeByte = rt.UnpackType(reflect.TypeOf(byte(0))) // HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 // characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 // so that the JSON will be safe to embed inside HTML