// 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 ( ) 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) Token { return Token{kind: kindString, str: } } // Int creates an int token. func ( int64) Token { return Token{kind: kindInt, i64: } } // Uint creates a uint token. func ( uint64) Token { return Token{kind: kindUint, u64: } } // Float creates a float token. func ( float64) Token { return Token{kind: kindFloat, f64: } } // Bool creates a bool token. func ( bool) Token { return Token{kind: kindBool, 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 ( io.Writer) *Encoder { := make([]context, 0, 8) = append(, context{isObject: false, needsComma: false, expectKey: false}) return &Encoder{ w: , stack: , } } // WriteToken writes a token to the encoder. func ( *Encoder) ( Token) error { if len(.stack) == 0 { return fmt.Errorf("empty stack") } := &.stack[len(.stack)-1] := .kind == kindObjectEnd || .kind == kindArrayEnd if ! && .needsComma { if , := .w.Write(commaByte); != nil { return } .needsComma = false } var error switch .kind { case kindString: := stringToBytes(.str) := false for , := range { if == '"' || == '\\' || < 0x20 { = true break } } if ! { if _, = .w.Write(quoteByte); != nil { return } if _, = .w.Write(); != nil { return } if _, = .w.Write(quoteByte); != nil { return } } else { if _, = .w.Write(quoteByte); != nil { return } for := 0; < len(.str); ++ { := .str[] switch { case '"': if _, = .w.Write(escapeQuote); != nil { return } case '\\': if _, = .w.Write(escapeBackslash); != nil { return } case '\b': if _, = .w.Write(escapeBackspace); != nil { return } case '\f': if _, = .w.Write(escapeFormfeed); != nil { return } case '\n': if _, = .w.Write(escapeNewline); != nil { return } case '\r': if _, = .w.Write(escapeCarriage); != nil { return } case '\t': if _, = .w.Write(escapeTab); != nil { return } default: if < 0x20 { if _, = .w.Write(escapeUnicode); != nil { return } if _, = .w.Write([]byte{hexDigits[>>4], hexDigits[&0xf]}); != nil { return } } else { if _, = .w.Write([]byte{}); != nil { return } } } } if _, = .w.Write(quoteByte); != nil { return } } if .isObject { if .expectKey { // key if _, = .w.Write(colonByte); != nil { return } .expectKey = false return nil // do not call afterValue for keys } else { // value .afterValue() } } else { .afterValue() } case kindInt: := strconv.AppendInt(.buf[:0], .i64, 10) if _, = .w.Write(); != nil { return } .afterValue() case kindUint: := strconv.AppendUint(.buf[:0], .u64, 10) if _, = .w.Write(); != nil { return } .afterValue() case kindFloat: := strconv.AppendFloat(.buf[:0], .f64, 'g', -1, 64) if _, = .w.Write(); != nil { return } .afterValue() case kindBool: if .b { if _, = .w.Write(trueByte); != nil { return } } else { if _, = .w.Write(falseByte); != nil { return } } .afterValue() case kindNull: if _, = .w.Write(nullByte); != nil { return } .afterValue() case kindObjectStart: if _, = .w.Write(openObjectByte); != nil { return } .stack = append(.stack, context{isObject: true, needsComma: false, expectKey: true}) return nil case kindObjectEnd: if _, = .w.Write(closeObjectByte); != nil { return } .stack = .stack[:len(.stack)-1] .afterValue() if len(.stack) == 1 { if _, = .w.Write(newlineByte); != nil { return } } return nil case kindArrayStart: if _, = .w.Write(openArrayByte); != nil { return } .stack = append(.stack, context{isObject: false, needsComma: false, expectKey: false}) return nil case kindArrayEnd: if _, = .w.Write(closeArrayByte); != nil { return } .stack = .stack[:len(.stack)-1] .afterValue() if len(.stack) == 1 { if _, = .w.Write(newlineByte); != nil { return } } return nil default: return fmt.Errorf("unknown token kind") } return } // afterValue updates the state after encoding a value func ( *Encoder) () { if len(.stack) > 1 { := &.stack[len(.stack)-1] .needsComma = true if .isObject { .expectKey = true } } } func stringToBytes( string) []byte { return unsafe.Slice(unsafe.StringData(), len()) }