...

Source file src/github.com/launchdarkly/go-jsonstream/v3/jwriter/token_writer_default.go

Documentation: github.com/launchdarkly/go-jsonstream/v3/jwriter

     1  //go:build !launchdarkly_easyjson
     2  // +build !launchdarkly_easyjson
     3  
     4  package jwriter
     5  
     6  import (
     7  	"encoding/json"
     8  	"io"
     9  	"strconv"
    10  	"unicode/utf8"
    11  )
    12  
    13  // This file defines the default implementation of the low-level JSON token writer. If the launchdarkly_easyjson
    14  // build tag is enabled, we use the easyjson adapter in token_writer_easyjson.go instead. These have the same
    15  // methods so the Writer code does not need to know which implementation we're using; however, we don't
    16  // actually define an interface for these, because calling the methods through an interface would limit
    17  // performance.
    18  
    19  var (
    20  	tokenNull  = []byte("null")  //nolint:gochecknoglobals
    21  	tokenTrue  = []byte("true")  //nolint:gochecknoglobals
    22  	tokenFalse = []byte("false") //nolint:gochecknoglobals
    23  )
    24  
    25  type tokenWriter struct {
    26  	buf       streamableBuffer
    27  	tempBytes [50]byte
    28  }
    29  
    30  func newTokenWriter() tokenWriter {
    31  	return tokenWriter{}
    32  }
    33  
    34  func newStreamingTokenWriter(dest io.Writer, bufferSize int) tokenWriter {
    35  	tw := tokenWriter{}
    36  	tw.buf.Grow(bufferSize)
    37  	tw.buf.SetStreamingWriter(dest, bufferSize)
    38  	return tw
    39  }
    40  
    41  // Bytes returns the full encoded byte slice.
    42  //
    43  // If the buffer is in a failed state from a previous invalid operation, Bytes() returns any data written
    44  // so far.
    45  func (tw *tokenWriter) Bytes() []byte {
    46  	return tw.buf.Bytes()
    47  }
    48  
    49  // Grow expands the internal buffer by the specified number of bytes. It is the same as calling Grow
    50  // on a bytes.Buffer.
    51  func (tw *tokenWriter) Grow(n int) {
    52  	tw.buf.Grow(n)
    53  }
    54  
    55  // Flush writes any remaining in-memory output to the underlying Writer, if this is a streaming buffer
    56  // created with newStreamingTokenWriter. It has no effect otherwise.
    57  func (tw *tokenWriter) Flush() error {
    58  	return tw.buf.Flush()
    59  }
    60  
    61  // Null writes a JSON null.
    62  func (tw *tokenWriter) Null() error {
    63  	tw.buf.Write(tokenNull)
    64  	return tw.buf.GetWriterError()
    65  }
    66  
    67  // Bool writes a JSON boolean.
    68  func (tw *tokenWriter) Bool(value bool) error {
    69  	var out []byte
    70  	if value {
    71  		out = tokenTrue
    72  	} else {
    73  		out = tokenFalse
    74  	}
    75  	tw.buf.Write(out)
    76  	return tw.buf.GetWriterError()
    77  }
    78  
    79  // Int writes an integer JSON number.
    80  func (tw *tokenWriter) Int(value int) error {
    81  	if value == 0 {
    82  		tw.buf.WriteByte('0')
    83  	} else {
    84  		out := tw.tempBytes[0:0]
    85  		out = strconv.AppendInt(out, int64(value), 10)
    86  		tw.buf.Write(out)
    87  	}
    88  	return tw.buf.GetWriterError()
    89  }
    90  
    91  // Float64 writes a JSON number.
    92  func (tw *tokenWriter) Float64(value float64) error {
    93  	if value == 0 {
    94  		tw.buf.WriteByte('0')
    95  	} else {
    96  		i := int(value)
    97  		if float64(i) == value {
    98  			return tw.Int(i)
    99  		}
   100  		out := tw.tempBytes[0:0]
   101  		out = strconv.AppendFloat(out, value, 'g', -1, 64)
   102  		tw.buf.Write(out)
   103  	}
   104  	return tw.buf.GetWriterError()
   105  }
   106  
   107  // String writes a JSON string.
   108  func (tw *tokenWriter) String(value string) error {
   109  	return tw.writeQuotedString(value)
   110  }
   111  
   112  // Raw writes a preformatted chunk of JSON data.
   113  func (tw *tokenWriter) Raw(value json.RawMessage) error {
   114  	tw.buf.Write(value)
   115  	return tw.buf.GetWriterError()
   116  }
   117  
   118  // PropertyName writes a JSON object property name followed by a colon.
   119  func (tw *tokenWriter) PropertyName(name string) error {
   120  	if err := tw.String(name); err != nil {
   121  		return err
   122  	}
   123  	tw.buf.WriteByte(':')
   124  	return tw.buf.GetWriterError()
   125  }
   126  
   127  // Delimiter writes a single character which must be a valid JSON delimiter ('{', ',', etc.).
   128  func (tw *tokenWriter) Delimiter(delimiter byte) error {
   129  	tw.buf.WriteByte(delimiter)
   130  	return tw.buf.GetWriterError()
   131  }
   132  
   133  func (tw *tokenWriter) writeQuotedString(s string) error {
   134  	// This is basically the same logic used internally by json.Marshal
   135  	tw.buf.WriteByte('"')
   136  	start := 0
   137  	for i := 0; i < len(s); {
   138  		aByte := s[i]
   139  		if aByte < ' ' || aByte == '"' || aByte == '\\' {
   140  			if i > start {
   141  				tw.buf.WriteString(s[start:i])
   142  			}
   143  			tw.writeEscapedChar(aByte)
   144  			i++
   145  			start = i
   146  		} else {
   147  			if aByte < utf8.RuneSelf { // single-byte character
   148  				i++
   149  			} else {
   150  				_, size := utf8.DecodeRuneInString(s[i:])
   151  				i += size
   152  			}
   153  		}
   154  	}
   155  	if start < len(s) {
   156  		tw.buf.WriteString(s[start:])
   157  	}
   158  	tw.buf.WriteByte('"')
   159  	return tw.buf.GetWriterError()
   160  }
   161  
   162  func (tw *tokenWriter) writeEscapedChar(ch byte) {
   163  	out := tw.tempBytes[0:2]
   164  	out[0] = '\\'
   165  	switch ch {
   166  	case '\b':
   167  		out[1] = 'b'
   168  	case '\t':
   169  		out[1] = 't'
   170  	case '\n':
   171  		out[1] = 'n'
   172  	case '\f':
   173  		out[1] = 'f'
   174  	case '\r':
   175  		out[1] = 'r'
   176  	case '"':
   177  		out[1] = '"'
   178  	case '\\':
   179  		out[1] = '\\'
   180  	default:
   181  		out[1] = 'u'
   182  		out = append(out, '0')
   183  		out = append(out, '0')
   184  		hexChars := make([]byte, 0, 4)
   185  		hexChars = strconv.AppendInt(hexChars, int64(ch), 16)
   186  		if len(hexChars) < 2 {
   187  			out = append(out, '0')
   188  		}
   189  		out = append(out, hexChars...)
   190  	}
   191  	tw.buf.Write(out)
   192  }
   193  

View as plain text