You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			287 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
			
		
		
	
	
			287 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
| package encoder
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"unsafe"
 | |
| 
 | |
| 	"github.com/goccy/go-json/internal/errors"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	isWhiteSpace = [256]bool{
 | |
| 		' ':  true,
 | |
| 		'\n': true,
 | |
| 		'\t': true,
 | |
| 		'\r': true,
 | |
| 	}
 | |
| 	isHTMLEscapeChar = [256]bool{
 | |
| 		'<': true,
 | |
| 		'>': true,
 | |
| 		'&': true,
 | |
| 	}
 | |
| 	nul = byte('\000')
 | |
| )
 | |
| 
 | |
| func Compact(buf *bytes.Buffer, src []byte, escape bool) error {
 | |
| 	if len(src) == 0 {
 | |
| 		return errors.ErrUnexpectedEndOfJSON("", 0)
 | |
| 	}
 | |
| 	buf.Grow(len(src))
 | |
| 	dst := buf.Bytes()
 | |
| 
 | |
| 	ctx := TakeRuntimeContext()
 | |
| 	ctxBuf := ctx.Buf[:0]
 | |
| 	ctxBuf = append(append(ctxBuf, src...), nul)
 | |
| 	ctx.Buf = ctxBuf
 | |
| 
 | |
| 	if err := compactAndWrite(buf, dst, ctxBuf, escape); err != nil {
 | |
| 		ReleaseRuntimeContext(ctx)
 | |
| 		return err
 | |
| 	}
 | |
| 	ReleaseRuntimeContext(ctx)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func compactAndWrite(buf *bytes.Buffer, dst []byte, src []byte, escape bool) error {
 | |
| 	dst, err := compact(dst, src, escape)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if _, err := buf.Write(dst); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func compact(dst, src []byte, escape bool) ([]byte, error) {
 | |
| 	buf, cursor, err := compactValue(dst, src, 0, escape)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if err := validateEndBuf(src, cursor); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return buf, nil
 | |
| }
 | |
| 
 | |
| func validateEndBuf(src []byte, cursor int64) error {
 | |
| 	for {
 | |
| 		switch src[cursor] {
 | |
| 		case ' ', '\t', '\n', '\r':
 | |
| 			cursor++
 | |
| 			continue
 | |
| 		case nul:
 | |
| 			return nil
 | |
| 		}
 | |
| 		return errors.ErrSyntax(
 | |
| 			fmt.Sprintf("invalid character '%c' after top-level value", src[cursor]),
 | |
| 			cursor+1,
 | |
| 		)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func skipWhiteSpace(buf []byte, cursor int64) int64 {
 | |
| LOOP:
 | |
| 	if isWhiteSpace[buf[cursor]] {
 | |
| 		cursor++
 | |
| 		goto LOOP
 | |
| 	}
 | |
| 	return cursor
 | |
| }
 | |
| 
 | |
| func compactValue(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
 | |
| 	for {
 | |
| 		switch src[cursor] {
 | |
| 		case ' ', '\t', '\n', '\r':
 | |
| 			cursor++
 | |
| 			continue
 | |
| 		case '{':
 | |
| 			return compactObject(dst, src, cursor, escape)
 | |
| 		case '}':
 | |
| 			return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor)
 | |
| 		case '[':
 | |
| 			return compactArray(dst, src, cursor, escape)
 | |
| 		case ']':
 | |
| 			return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor)
 | |
| 		case '"':
 | |
| 			return compactString(dst, src, cursor, escape)
 | |
| 		case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
 | |
| 			return compactNumber(dst, src, cursor)
 | |
| 		case 't':
 | |
| 			return compactTrue(dst, src, cursor)
 | |
| 		case 'f':
 | |
| 			return compactFalse(dst, src, cursor)
 | |
| 		case 'n':
 | |
| 			return compactNull(dst, src, cursor)
 | |
| 		default:
 | |
| 			return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func compactObject(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
 | |
| 	if src[cursor] == '{' {
 | |
| 		dst = append(dst, '{')
 | |
| 	} else {
 | |
| 		return nil, 0, errors.ErrExpected("expected { character for object value", cursor)
 | |
| 	}
 | |
| 	cursor = skipWhiteSpace(src, cursor+1)
 | |
| 	if src[cursor] == '}' {
 | |
| 		dst = append(dst, '}')
 | |
| 		return dst, cursor + 1, nil
 | |
| 	}
 | |
| 	var err error
 | |
| 	for {
 | |
| 		cursor = skipWhiteSpace(src, cursor)
 | |
| 		dst, cursor, err = compactString(dst, src, cursor, escape)
 | |
| 		if err != nil {
 | |
| 			return nil, 0, err
 | |
| 		}
 | |
| 		cursor = skipWhiteSpace(src, cursor)
 | |
| 		if src[cursor] != ':' {
 | |
| 			return nil, 0, errors.ErrExpected("colon after object key", cursor)
 | |
| 		}
 | |
| 		dst = append(dst, ':')
 | |
| 		dst, cursor, err = compactValue(dst, src, cursor+1, escape)
 | |
| 		if err != nil {
 | |
| 			return nil, 0, err
 | |
| 		}
 | |
| 		cursor = skipWhiteSpace(src, cursor)
 | |
| 		switch src[cursor] {
 | |
| 		case '}':
 | |
| 			dst = append(dst, '}')
 | |
| 			cursor++
 | |
| 			return dst, cursor, nil
 | |
| 		case ',':
 | |
| 			dst = append(dst, ',')
 | |
| 		default:
 | |
| 			return nil, 0, errors.ErrExpected("comma after object value", cursor)
 | |
| 		}
 | |
| 		cursor++
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func compactArray(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
 | |
| 	if src[cursor] == '[' {
 | |
| 		dst = append(dst, '[')
 | |
| 	} else {
 | |
| 		return nil, 0, errors.ErrExpected("expected [ character for array value", cursor)
 | |
| 	}
 | |
| 	cursor = skipWhiteSpace(src, cursor+1)
 | |
| 	if src[cursor] == ']' {
 | |
| 		dst = append(dst, ']')
 | |
| 		return dst, cursor + 1, nil
 | |
| 	}
 | |
| 	var err error
 | |
| 	for {
 | |
| 		dst, cursor, err = compactValue(dst, src, cursor, escape)
 | |
| 		if err != nil {
 | |
| 			return nil, 0, err
 | |
| 		}
 | |
| 		cursor = skipWhiteSpace(src, cursor)
 | |
| 		switch src[cursor] {
 | |
| 		case ']':
 | |
| 			dst = append(dst, ']')
 | |
| 			cursor++
 | |
| 			return dst, cursor, nil
 | |
| 		case ',':
 | |
| 			dst = append(dst, ',')
 | |
| 		default:
 | |
| 			return nil, 0, errors.ErrExpected("comma after array value", cursor)
 | |
| 		}
 | |
| 		cursor++
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
 | |
| 	if src[cursor] != '"' {
 | |
| 		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "string", cursor)
 | |
| 	}
 | |
| 	start := cursor
 | |
| 	for {
 | |
| 		cursor++
 | |
| 		c := src[cursor]
 | |
| 		if escape {
 | |
| 			if isHTMLEscapeChar[c] {
 | |
| 				dst = append(dst, src[start:cursor]...)
 | |
| 				dst = append(dst, `\u00`...)
 | |
| 				dst = append(dst, hex[c>>4], hex[c&0xF])
 | |
| 				start = cursor + 1
 | |
| 			} else if c == 0xE2 && cursor+2 < int64(len(src)) && src[cursor+1] == 0x80 && src[cursor+2]&^1 == 0xA8 {
 | |
| 				dst = append(dst, src[start:cursor]...)
 | |
| 				dst = append(dst, `\u202`...)
 | |
| 				dst = append(dst, hex[src[cursor+2]&0xF])
 | |
| 				cursor += 2
 | |
| 				start = cursor + 3
 | |
| 			}
 | |
| 		}
 | |
| 		switch c {
 | |
| 		case '\\':
 | |
| 			cursor++
 | |
| 			if src[cursor] == nul {
 | |
| 				return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
 | |
| 			}
 | |
| 		case '"':
 | |
| 			cursor++
 | |
| 			return append(dst, src[start:cursor]...), cursor, nil
 | |
| 		case nul:
 | |
| 			return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func compactNumber(dst, src []byte, cursor int64) ([]byte, int64, error) {
 | |
| 	start := cursor
 | |
| 	for {
 | |
| 		cursor++
 | |
| 		if floatTable[src[cursor]] {
 | |
| 			continue
 | |
| 		}
 | |
| 		break
 | |
| 	}
 | |
| 	num := src[start:cursor]
 | |
| 	if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&num)), 64); err != nil {
 | |
| 		return nil, 0, err
 | |
| 	}
 | |
| 	dst = append(dst, num...)
 | |
| 	return dst, cursor, nil
 | |
| }
 | |
| 
 | |
| func compactTrue(dst, src []byte, cursor int64) ([]byte, int64, error) {
 | |
| 	if cursor+3 >= int64(len(src)) {
 | |
| 		return nil, 0, errors.ErrUnexpectedEndOfJSON("true", cursor)
 | |
| 	}
 | |
| 	if !bytes.Equal(src[cursor:cursor+4], []byte(`true`)) {
 | |
| 		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "true", cursor)
 | |
| 	}
 | |
| 	dst = append(dst, "true"...)
 | |
| 	cursor += 4
 | |
| 	return dst, cursor, nil
 | |
| }
 | |
| 
 | |
| func compactFalse(dst, src []byte, cursor int64) ([]byte, int64, error) {
 | |
| 	if cursor+4 >= int64(len(src)) {
 | |
| 		return nil, 0, errors.ErrUnexpectedEndOfJSON("false", cursor)
 | |
| 	}
 | |
| 	if !bytes.Equal(src[cursor:cursor+5], []byte(`false`)) {
 | |
| 		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "false", cursor)
 | |
| 	}
 | |
| 	dst = append(dst, "false"...)
 | |
| 	cursor += 5
 | |
| 	return dst, cursor, nil
 | |
| }
 | |
| 
 | |
| func compactNull(dst, src []byte, cursor int64) ([]byte, int64, error) {
 | |
| 	if cursor+3 >= int64(len(src)) {
 | |
| 		return nil, 0, errors.ErrUnexpectedEndOfJSON("null", cursor)
 | |
| 	}
 | |
| 	if !bytes.Equal(src[cursor:cursor+4], []byte(`null`)) {
 | |
| 		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "null", cursor)
 | |
| 	}
 | |
| 	dst = append(dst, "null"...)
 | |
| 	cursor += 4
 | |
| 	return dst, cursor, nil
 | |
| }
 |