package json
import (
"bytes"
"errors"
"io"
)
type Decoder struct {
r io .Reader
buf []byte
d decodeState
scanp int
scanned int64
scan scanner
err error
tokenState int
tokenStack []int
}
func NewDecoder (r io .Reader ) *Decoder {
return &Decoder {r : r }
}
func (dec *Decoder ) UseNumber () { dec .d .useNumber = true }
func (dec *Decoder ) DisallowUnknownFields () { dec .d .disallowUnknownFields = true }
func (dec *Decoder ) Decode (v any ) error {
if dec .err != nil {
return dec .err
}
if err := dec .tokenPrepareForDecode (); err != nil {
return err
}
if !dec .tokenValueAllowed () {
return &SyntaxError {msg : "not at beginning of value" , Offset : dec .InputOffset ()}
}
n , err := dec .readValue ()
if err != nil {
return err
}
dec .d .init (dec .buf [dec .scanp : dec .scanp +n ])
dec .scanp += n
err = dec .d .unmarshal (v )
dec .tokenValueEnd ()
return err
}
func (dec *Decoder ) Buffered () io .Reader {
return bytes .NewReader (dec .buf [dec .scanp :])
}
func (dec *Decoder ) readValue () (int , error ) {
dec .scan .reset ()
scanp := dec .scanp
var err error
Input :
for scanp >= 0 {
for ; scanp < len (dec .buf ); scanp ++ {
c := dec .buf [scanp ]
dec .scan .bytes ++
switch dec .scan .step (&dec .scan , c ) {
case scanEnd :
dec .scan .bytes --
break Input
case scanEndObject , scanEndArray :
if stateEndValue (&dec .scan , ' ' ) == scanEnd {
scanp ++
break Input
}
case scanError :
dec .err = dec .scan .err
return 0 , dec .scan .err
}
}
if err != nil {
if err == io .EOF {
if dec .scan .step (&dec .scan , ' ' ) == scanEnd {
break Input
}
if nonSpace (dec .buf ) {
err = io .ErrUnexpectedEOF
}
}
dec .err = err
return 0 , err
}
n := scanp - dec .scanp
err = dec .refill ()
scanp = dec .scanp + n
}
return scanp - dec .scanp , nil
}
func (dec *Decoder ) refill () error {
if dec .scanp > 0 {
dec .scanned += int64 (dec .scanp )
n := copy (dec .buf , dec .buf [dec .scanp :])
dec .buf = dec .buf [:n ]
dec .scanp = 0
}
const minRead = 512
if cap (dec .buf )-len (dec .buf ) < minRead {
newBuf := make ([]byte , len (dec .buf ), 2 *cap (dec .buf )+minRead )
copy (newBuf , dec .buf )
dec .buf = newBuf
}
n , err := dec .r .Read (dec .buf [len (dec .buf ):cap (dec .buf )])
dec .buf = dec .buf [0 : len (dec .buf )+n ]
return err
}
func nonSpace (b []byte ) bool {
for _ , c := range b {
if !isSpace (c ) {
return true
}
}
return false
}
type Encoder struct {
w io .Writer
err error
escapeHTML bool
indentBuf []byte
indentPrefix string
indentValue string
}
func NewEncoder (w io .Writer ) *Encoder {
return &Encoder {w : w , escapeHTML : true }
}
func (enc *Encoder ) Encode (v any ) error {
if enc .err != nil {
return enc .err
}
e := newEncodeState ()
defer encodeStatePool .Put (e )
err := e .marshal (v , encOpts {escapeHTML : enc .escapeHTML })
if err != nil {
return err
}
e .WriteByte ('\n' )
b := e .Bytes ()
if enc .indentPrefix != "" || enc .indentValue != "" {
enc .indentBuf , err = appendIndent (enc .indentBuf [:0 ], b , enc .indentPrefix , enc .indentValue )
if err != nil {
return err
}
b = enc .indentBuf
}
if _, err = enc .w .Write (b ); err != nil {
enc .err = err
}
return err
}
func (enc *Encoder ) SetIndent (prefix , indent string ) {
enc .indentPrefix = prefix
enc .indentValue = indent
}
func (enc *Encoder ) SetEscapeHTML (on bool ) {
enc .escapeHTML = on
}
type RawMessage []byte
func (m RawMessage ) MarshalJSON () ([]byte , error ) {
if m == nil {
return []byte ("null" ), nil
}
return m , nil
}
func (m *RawMessage ) UnmarshalJSON (data []byte ) error {
if m == nil {
return errors .New ("json.RawMessage: UnmarshalJSON on nil pointer" )
}
*m = append ((*m )[0 :0 ], data ...)
return nil
}
var _ Marshaler = (*RawMessage )(nil )
var _ Unmarshaler = (*RawMessage )(nil )
type Token any
const (
tokenTopValue = iota
tokenArrayStart
tokenArrayValue
tokenArrayComma
tokenObjectStart
tokenObjectKey
tokenObjectColon
tokenObjectValue
tokenObjectComma
)
func (dec *Decoder ) tokenPrepareForDecode () error {
switch dec .tokenState {
case tokenArrayComma :
c , err := dec .peek ()
if err != nil {
return err
}
if c != ',' {
return &SyntaxError {"expected comma after array element" , dec .InputOffset ()}
}
dec .scanp ++
dec .tokenState = tokenArrayValue
case tokenObjectColon :
c , err := dec .peek ()
if err != nil {
return err
}
if c != ':' {
return &SyntaxError {"expected colon after object key" , dec .InputOffset ()}
}
dec .scanp ++
dec .tokenState = tokenObjectValue
}
return nil
}
func (dec *Decoder ) tokenValueAllowed () bool {
switch dec .tokenState {
case tokenTopValue , tokenArrayStart , tokenArrayValue , tokenObjectValue :
return true
}
return false
}
func (dec *Decoder ) tokenValueEnd () {
switch dec .tokenState {
case tokenArrayStart , tokenArrayValue :
dec .tokenState = tokenArrayComma
case tokenObjectValue :
dec .tokenState = tokenObjectComma
}
}
type Delim rune
func (d Delim ) String () string {
return string (d )
}
func (dec *Decoder ) Token () (Token , error ) {
for {
c , err := dec .peek ()
if err != nil {
return nil , err
}
switch c {
case '[' :
if !dec .tokenValueAllowed () {
return dec .tokenError (c )
}
dec .scanp ++
dec .tokenStack = append (dec .tokenStack , dec .tokenState )
dec .tokenState = tokenArrayStart
return Delim ('[' ), nil
case ']' :
if dec .tokenState != tokenArrayStart && dec .tokenState != tokenArrayComma {
return dec .tokenError (c )
}
dec .scanp ++
dec .tokenState = dec .tokenStack [len (dec .tokenStack )-1 ]
dec .tokenStack = dec .tokenStack [:len (dec .tokenStack )-1 ]
dec .tokenValueEnd ()
return Delim (']' ), nil
case '{' :
if !dec .tokenValueAllowed () {
return dec .tokenError (c )
}
dec .scanp ++
dec .tokenStack = append (dec .tokenStack , dec .tokenState )
dec .tokenState = tokenObjectStart
return Delim ('{' ), nil
case '}' :
if dec .tokenState != tokenObjectStart && dec .tokenState != tokenObjectComma {
return dec .tokenError (c )
}
dec .scanp ++
dec .tokenState = dec .tokenStack [len (dec .tokenStack )-1 ]
dec .tokenStack = dec .tokenStack [:len (dec .tokenStack )-1 ]
dec .tokenValueEnd ()
return Delim ('}' ), nil
case ':' :
if dec .tokenState != tokenObjectColon {
return dec .tokenError (c )
}
dec .scanp ++
dec .tokenState = tokenObjectValue
continue
case ',' :
if dec .tokenState == tokenArrayComma {
dec .scanp ++
dec .tokenState = tokenArrayValue
continue
}
if dec .tokenState == tokenObjectComma {
dec .scanp ++
dec .tokenState = tokenObjectKey
continue
}
return dec .tokenError (c )
case '"' :
if dec .tokenState == tokenObjectStart || dec .tokenState == tokenObjectKey {
var x string
old := dec .tokenState
dec .tokenState = tokenTopValue
err := dec .Decode (&x )
dec .tokenState = old
if err != nil {
return nil , err
}
dec .tokenState = tokenObjectColon
return x , nil
}
fallthrough
default :
if !dec .tokenValueAllowed () {
return dec .tokenError (c )
}
var x any
if err := dec .Decode (&x ); err != nil {
return nil , err
}
return x , nil
}
}
}
func (dec *Decoder ) tokenError (c byte ) (Token , error ) {
var context string
switch dec .tokenState {
case tokenTopValue :
context = " looking for beginning of value"
case tokenArrayStart , tokenArrayValue , tokenObjectValue :
context = " looking for beginning of value"
case tokenArrayComma :
context = " after array element"
case tokenObjectKey :
context = " looking for beginning of object key string"
case tokenObjectColon :
context = " after object key"
case tokenObjectComma :
context = " after object key:value pair"
}
return nil , &SyntaxError {"invalid character " + quoteChar (c ) + context , dec .InputOffset ()}
}
func (dec *Decoder ) More () bool {
c , err := dec .peek ()
return err == nil && c != ']' && c != '}'
}
func (dec *Decoder ) peek () (byte , error ) {
var err error
for {
for i := dec .scanp ; i < len (dec .buf ); i ++ {
c := dec .buf [i ]
if isSpace (c ) {
continue
}
dec .scanp = i
return c , nil
}
if err != nil {
return 0 , err
}
err = dec .refill ()
}
}
func (dec *Decoder ) InputOffset () int64 {
return dec .scanned + int64 (dec .scanp )
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .