forked from quic-go/quic-go
325 lines
7.1 KiB
Go
325 lines
7.1 KiB
Go
// Package jsontext provides a fast JSON encoder providing only the necessary features
|
|
// for qlog encoding. No efforts are made to add any features beyond qlog's requirements.
|
|
//
|
|
// The API aims to be compatible with the standard library's encoding/json/jsontext package.
|
|
package jsontext
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"unsafe"
|
|
)
|
|
|
|
type kind uint8
|
|
|
|
const (
|
|
kindString kind = iota
|
|
kindInt
|
|
kindUint
|
|
kindFloat
|
|
kindBool
|
|
kindNull
|
|
kindObjectStart
|
|
kindObjectEnd
|
|
kindArrayStart
|
|
kindArrayEnd
|
|
)
|
|
|
|
// Token represents a JSON token.
|
|
type Token struct {
|
|
kind kind
|
|
str string
|
|
i64 int64
|
|
u64 uint64
|
|
f64 float64
|
|
b bool
|
|
}
|
|
|
|
// String creates a string token.
|
|
func String(s string) Token {
|
|
return Token{kind: kindString, str: s}
|
|
}
|
|
|
|
// Int creates an int token.
|
|
func Int(i int64) Token {
|
|
return Token{kind: kindInt, i64: i}
|
|
}
|
|
|
|
// Uint creates a uint token.
|
|
func Uint(u uint64) Token {
|
|
return Token{kind: kindUint, u64: u}
|
|
}
|
|
|
|
// Float creates a float token.
|
|
func Float(f float64) Token {
|
|
return Token{kind: kindFloat, f64: f}
|
|
}
|
|
|
|
// Bool creates a bool token.
|
|
func Bool(b bool) Token {
|
|
return Token{kind: kindBool, b: b}
|
|
}
|
|
|
|
// Null is a null token.
|
|
var Null Token = Token{kind: kindNull}
|
|
|
|
// BeginObject is the begin object token.
|
|
var BeginObject Token = Token{kind: kindObjectStart}
|
|
|
|
// EndObject is the end object token.
|
|
var EndObject Token = Token{kind: kindObjectEnd}
|
|
|
|
// BeginArray is the begin array token.
|
|
var BeginArray Token = Token{kind: kindArrayStart}
|
|
|
|
// EndArray is the end array token.
|
|
var EndArray Token = Token{kind: kindArrayEnd}
|
|
|
|
// True is a true token.
|
|
var True Token = Bool(true)
|
|
|
|
// False is a false token.
|
|
var False Token = Bool(false)
|
|
|
|
var hexDigits = [16]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
|
|
|
|
var (
|
|
commaByte = []byte(",")
|
|
quoteByte = []byte(`"`)
|
|
colonByte = []byte(":")
|
|
trueByte = []byte("true")
|
|
falseByte = []byte("false")
|
|
nullByte = []byte("null")
|
|
openObjectByte = []byte("{")
|
|
closeObjectByte = []byte("}")
|
|
openArrayByte = []byte("[")
|
|
closeArrayByte = []byte("]")
|
|
newlineByte = []byte("\n")
|
|
escapeQuote = []byte(`\"`)
|
|
escapeBackslash = []byte(`\\`)
|
|
escapeBackspace = []byte(`\b`)
|
|
escapeFormfeed = []byte(`\f`)
|
|
escapeNewline = []byte(`\n`)
|
|
escapeCarriage = []byte(`\r`)
|
|
escapeTab = []byte(`\t`)
|
|
escapeUnicode = []byte(`\u00`)
|
|
)
|
|
|
|
type context struct {
|
|
isObject bool
|
|
needsComma bool
|
|
expectKey bool
|
|
}
|
|
|
|
// Encoder encodes JSON to an io.Writer.
|
|
type Encoder struct {
|
|
w io.Writer
|
|
buf [64]byte // scratch buffer for number formatting
|
|
stack []context
|
|
}
|
|
|
|
// NewEncoder creates a new Encoder.
|
|
func NewEncoder(w io.Writer) *Encoder {
|
|
stack := make([]context, 0, 8)
|
|
stack = append(stack, context{isObject: false, needsComma: false, expectKey: false})
|
|
return &Encoder{
|
|
w: w,
|
|
stack: stack,
|
|
}
|
|
}
|
|
|
|
// WriteToken writes a token to the encoder.
|
|
func (e *Encoder) WriteToken(t Token) error {
|
|
if len(e.stack) == 0 {
|
|
return fmt.Errorf("empty stack")
|
|
}
|
|
curr := &e.stack[len(e.stack)-1]
|
|
isClosing := t.kind == kindObjectEnd || t.kind == kindArrayEnd
|
|
if !isClosing && curr.needsComma {
|
|
if _, err := e.w.Write(commaByte); err != nil {
|
|
return err
|
|
}
|
|
curr.needsComma = false
|
|
}
|
|
var err error
|
|
switch t.kind {
|
|
case kindString:
|
|
data := stringToBytes(t.str)
|
|
needsEscape := false
|
|
for _, b := range data {
|
|
if b == '"' || b == '\\' || b < 0x20 {
|
|
needsEscape = true
|
|
break
|
|
}
|
|
}
|
|
if !needsEscape {
|
|
if _, err = e.w.Write(quoteByte); err != nil {
|
|
return err
|
|
}
|
|
if _, err = e.w.Write(data); err != nil {
|
|
return err
|
|
}
|
|
if _, err = e.w.Write(quoteByte); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if _, err = e.w.Write(quoteByte); err != nil {
|
|
return err
|
|
}
|
|
for i := 0; i < len(t.str); i++ {
|
|
c := t.str[i]
|
|
switch c {
|
|
case '"':
|
|
if _, err = e.w.Write(escapeQuote); err != nil {
|
|
return err
|
|
}
|
|
case '\\':
|
|
if _, err = e.w.Write(escapeBackslash); err != nil {
|
|
return err
|
|
}
|
|
case '\b':
|
|
if _, err = e.w.Write(escapeBackspace); err != nil {
|
|
return err
|
|
}
|
|
case '\f':
|
|
if _, err = e.w.Write(escapeFormfeed); err != nil {
|
|
return err
|
|
}
|
|
case '\n':
|
|
if _, err = e.w.Write(escapeNewline); err != nil {
|
|
return err
|
|
}
|
|
case '\r':
|
|
if _, err = e.w.Write(escapeCarriage); err != nil {
|
|
return err
|
|
}
|
|
case '\t':
|
|
if _, err = e.w.Write(escapeTab); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
if c < 0x20 {
|
|
if _, err = e.w.Write(escapeUnicode); err != nil {
|
|
return err
|
|
}
|
|
if _, err = e.w.Write([]byte{hexDigits[c>>4], hexDigits[c&0xf]}); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if _, err = e.w.Write([]byte{c}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if _, err = e.w.Write(quoteByte); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if curr.isObject {
|
|
if curr.expectKey {
|
|
// key
|
|
if _, err = e.w.Write(colonByte); err != nil {
|
|
return err
|
|
}
|
|
curr.expectKey = false
|
|
return nil // do not call afterValue for keys
|
|
} else {
|
|
// value
|
|
e.afterValue()
|
|
}
|
|
} else {
|
|
e.afterValue()
|
|
}
|
|
case kindInt:
|
|
b := strconv.AppendInt(e.buf[:0], t.i64, 10)
|
|
if _, err = e.w.Write(b); err != nil {
|
|
return err
|
|
}
|
|
e.afterValue()
|
|
case kindUint:
|
|
b := strconv.AppendUint(e.buf[:0], t.u64, 10)
|
|
if _, err = e.w.Write(b); err != nil {
|
|
return err
|
|
}
|
|
e.afterValue()
|
|
case kindFloat:
|
|
b := strconv.AppendFloat(e.buf[:0], t.f64, 'g', -1, 64)
|
|
if _, err = e.w.Write(b); err != nil {
|
|
return err
|
|
}
|
|
e.afterValue()
|
|
case kindBool:
|
|
if t.b {
|
|
if _, err = e.w.Write(trueByte); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if _, err = e.w.Write(falseByte); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
e.afterValue()
|
|
case kindNull:
|
|
if _, err = e.w.Write(nullByte); err != nil {
|
|
return err
|
|
}
|
|
e.afterValue()
|
|
case kindObjectStart:
|
|
if _, err = e.w.Write(openObjectByte); err != nil {
|
|
return err
|
|
}
|
|
e.stack = append(e.stack, context{isObject: true, needsComma: false, expectKey: true})
|
|
return nil
|
|
case kindObjectEnd:
|
|
if _, err = e.w.Write(closeObjectByte); err != nil {
|
|
return err
|
|
}
|
|
e.stack = e.stack[:len(e.stack)-1]
|
|
e.afterValue()
|
|
if len(e.stack) == 1 {
|
|
if _, err = e.w.Write(newlineByte); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
case kindArrayStart:
|
|
if _, err = e.w.Write(openArrayByte); err != nil {
|
|
return err
|
|
}
|
|
e.stack = append(e.stack, context{isObject: false, needsComma: false, expectKey: false})
|
|
return nil
|
|
case kindArrayEnd:
|
|
if _, err = e.w.Write(closeArrayByte); err != nil {
|
|
return err
|
|
}
|
|
e.stack = e.stack[:len(e.stack)-1]
|
|
e.afterValue()
|
|
if len(e.stack) == 1 {
|
|
if _, err = e.w.Write(newlineByte); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unknown token kind")
|
|
}
|
|
return err
|
|
}
|
|
|
|
// afterValue updates the state after encoding a value
|
|
func (e *Encoder) afterValue() {
|
|
if len(e.stack) > 1 {
|
|
curr := &e.stack[len(e.stack)-1]
|
|
curr.needsComma = true
|
|
if curr.isObject {
|
|
curr.expectKey = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func stringToBytes(s string) []byte {
|
|
return unsafe.Slice(unsafe.StringData(s), len(s))
|
|
}
|