github.com/3JoB/go-json@v0.10.4/internal/encoder/compact.go (about)

     1  package encoder
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strconv"
     7  	"unsafe"
     8  
     9  	"github.com/3JoB/go-json/internal/errors"
    10  	"github.com/3JoB/unsafeConvert"
    11  )
    12  
    13  var (
    14  	isWhiteSpace = [256]bool{
    15  		' ':  true,
    16  		'\n': true,
    17  		'\t': true,
    18  		'\r': true,
    19  	}
    20  	isHTMLEscapeChar = [256]bool{
    21  		'<': true,
    22  		'>': true,
    23  		'&': true,
    24  	}
    25  	nul = byte('\000')
    26  )
    27  
    28  func Compact(buf *bytes.Buffer, src []byte, escape bool) error {
    29  	if len(src) == 0 {
    30  		return errors.ErrUnexpectedEndOfJSON("", 0)
    31  	}
    32  	buf.Grow(len(src))
    33  	dst := buf.Bytes()
    34  
    35  	ctx := TakeRuntimeContext()
    36  	ctxBuf := ctx.Buf[:0]
    37  	ctxBuf = append(append(ctxBuf, src...), nul)
    38  	ctx.Buf = ctxBuf
    39  
    40  	if err := compactAndWrite(buf, dst, ctxBuf, escape); err != nil {
    41  		ReleaseRuntimeContext(ctx)
    42  		return err
    43  	}
    44  	ReleaseRuntimeContext(ctx)
    45  	return nil
    46  }
    47  
    48  func compactAndWrite(buf *bytes.Buffer, dst []byte, src []byte, escape bool) error {
    49  	dst, err := compact(dst, src, escape)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	if _, err := buf.Write(dst); err != nil {
    54  		return err
    55  	}
    56  	return nil
    57  }
    58  
    59  func compact(dst, src []byte, escape bool) ([]byte, error) {
    60  	buf, cursor, err := compactValue(dst, src, 0, escape)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	if err := validateEndBuf(src, cursor); err != nil {
    65  		return nil, err
    66  	}
    67  	return buf, nil
    68  }
    69  
    70  func validateEndBuf(src []byte, cursor int64) error {
    71  	for {
    72  		switch src[cursor] {
    73  		case ' ', '\t', '\n', '\r':
    74  			cursor++
    75  			continue
    76  		case nul:
    77  			return nil
    78  		}
    79  		return errors.ErrSyntax(
    80  			fmt.Sprintf("invalid character '%c' after top-level value", src[cursor]),
    81  			cursor+1,
    82  		)
    83  	}
    84  }
    85  
    86  func skipWhiteSpace(buf []byte, cursor int64) int64 {
    87  LOOP:
    88  	if isWhiteSpace[buf[cursor]] {
    89  		cursor++
    90  		goto LOOP
    91  	}
    92  	return cursor
    93  }
    94  
    95  func compactValue(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
    96  	for {
    97  		switch src[cursor] {
    98  		case ' ', '\t', '\n', '\r':
    99  			cursor++
   100  			continue
   101  		case '{':
   102  			return compactObject(dst, src, cursor, escape)
   103  		case '}':
   104  			return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor)
   105  		case '[':
   106  			return compactArray(dst, src, cursor, escape)
   107  		case ']':
   108  			return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor)
   109  		case '"':
   110  			return compactString(dst, src, cursor, escape)
   111  		case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   112  			return compactNumber(dst, src, cursor)
   113  		case 't':
   114  			return compactTrue(dst, src, cursor)
   115  		case 'f':
   116  			return compactFalse(dst, src, cursor)
   117  		case 'n':
   118  			return compactNull(dst, src, cursor)
   119  		default:
   120  			return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor)
   121  		}
   122  	}
   123  }
   124  
   125  func compactObject(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
   126  	if src[cursor] == '{' {
   127  		dst = append(dst, '{')
   128  	} else {
   129  		return nil, 0, errors.ErrExpected("expected { character for object value", cursor)
   130  	}
   131  	cursor = skipWhiteSpace(src, cursor+1)
   132  	if src[cursor] == '}' {
   133  		dst = append(dst, '}')
   134  		return dst, cursor + 1, nil
   135  	}
   136  	var err error
   137  	for {
   138  		cursor = skipWhiteSpace(src, cursor)
   139  		dst, cursor, err = compactString(dst, src, cursor, escape)
   140  		if err != nil {
   141  			return nil, 0, err
   142  		}
   143  		cursor = skipWhiteSpace(src, cursor)
   144  		if src[cursor] != ':' {
   145  			return nil, 0, errors.ErrExpected("colon after object key", cursor)
   146  		}
   147  		dst = append(dst, ':')
   148  		dst, cursor, err = compactValue(dst, src, cursor+1, escape)
   149  		if err != nil {
   150  			return nil, 0, err
   151  		}
   152  		cursor = skipWhiteSpace(src, cursor)
   153  		switch src[cursor] {
   154  		case '}':
   155  			dst = append(dst, '}')
   156  			cursor++
   157  			return dst, cursor, nil
   158  		case ',':
   159  			dst = append(dst, ',')
   160  		default:
   161  			return nil, 0, errors.ErrExpected("comma after object value", cursor)
   162  		}
   163  		cursor++
   164  	}
   165  }
   166  
   167  func compactArray(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
   168  	if src[cursor] == '[' {
   169  		dst = append(dst, '[')
   170  	} else {
   171  		return nil, 0, errors.ErrExpected("expected [ character for array value", cursor)
   172  	}
   173  	cursor = skipWhiteSpace(src, cursor+1)
   174  	if src[cursor] == ']' {
   175  		dst = append(dst, ']')
   176  		return dst, cursor + 1, nil
   177  	}
   178  	var err error
   179  	for {
   180  		dst, cursor, err = compactValue(dst, src, cursor, escape)
   181  		if err != nil {
   182  			return nil, 0, err
   183  		}
   184  		cursor = skipWhiteSpace(src, cursor)
   185  		switch src[cursor] {
   186  		case ']':
   187  			dst = append(dst, ']')
   188  			cursor++
   189  			return dst, cursor, nil
   190  		case ',':
   191  			dst = append(dst, ',')
   192  		default:
   193  			return nil, 0, errors.ErrExpected("comma after array value", cursor)
   194  		}
   195  		cursor++
   196  	}
   197  }
   198  
   199  func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
   200  	if src[cursor] != '"' {
   201  		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "string", cursor)
   202  	}
   203  	start := cursor
   204  	for {
   205  		cursor++
   206  		c := src[cursor]
   207  		if escape {
   208  			if isHTMLEscapeChar[c] {
   209  				dst = append(dst, src[start:cursor]...)
   210  				dst = append(dst, `\u00`...)
   211  				dst = append(dst, hex[c>>4], hex[c&0xF])
   212  				start = cursor + 1
   213  			} else if c == 0xE2 && cursor+2 < int64(len(src)) && src[cursor+1] == 0x80 && src[cursor+2]&^1 == 0xA8 {
   214  				dst = append(dst, src[start:cursor]...)
   215  				dst = append(dst, `\u202`...)
   216  				dst = append(dst, hex[src[cursor+2]&0xF])
   217  				cursor += 2
   218  				start = cursor + 3
   219  			}
   220  		}
   221  		switch c {
   222  		case '\\':
   223  			cursor++
   224  			if src[cursor] == nul {
   225  				return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
   226  			}
   227  		case '"':
   228  			cursor++
   229  			return append(dst, src[start:cursor]...), cursor, nil
   230  		case nul:
   231  			return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
   232  		}
   233  	}
   234  }
   235  
   236  func compactNumber(dst, src []byte, cursor int64) ([]byte, int64, error) {
   237  	start := cursor
   238  	for {
   239  		cursor++
   240  		if floatTable[src[cursor]] {
   241  			continue
   242  		}
   243  		break
   244  	}
   245  	num := src[start:cursor]
   246  	if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&num)), 64); err != nil {
   247  		return nil, 0, err
   248  	}
   249  	dst = append(dst, num...)
   250  	return dst, cursor, nil
   251  }
   252  
   253  func compactTrue(dst, src []byte, cursor int64) ([]byte, int64, error) {
   254  	if cursor+3 >= int64(len(src)) {
   255  		return nil, 0, errors.ErrUnexpectedEndOfJSON("true", cursor)
   256  	}
   257  	if !bytes.Equal(src[cursor:cursor+4], unsafeConvert.BytesReflect(`true`)) {
   258  		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "true", cursor)
   259  	}
   260  	dst = append(dst, "true"...)
   261  	cursor += 4
   262  	return dst, cursor, nil
   263  }
   264  
   265  func compactFalse(dst, src []byte, cursor int64) ([]byte, int64, error) {
   266  	if cursor+4 >= int64(len(src)) {
   267  		return nil, 0, errors.ErrUnexpectedEndOfJSON("false", cursor)
   268  	}
   269  	if !bytes.Equal(src[cursor:cursor+5], unsafeConvert.BytesReflect(`false`)) {
   270  		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "false", cursor)
   271  	}
   272  	dst = append(dst, "false"...)
   273  	cursor += 5
   274  	return dst, cursor, nil
   275  }
   276  
   277  func compactNull(dst, src []byte, cursor int64) ([]byte, int64, error) {
   278  	if cursor+3 >= int64(len(src)) {
   279  		return nil, 0, errors.ErrUnexpectedEndOfJSON("null", cursor)
   280  	}
   281  	if !bytes.Equal(src[cursor:cursor+4], unsafeConvert.BytesReflect(`null`)) {
   282  		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "null", cursor)
   283  	}
   284  	dst = append(dst, "null"...)
   285  	cursor += 4
   286  	return dst, cursor, nil
   287  }