github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/util/json_writer.go (about)

     1  // Copyright 2024 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package util
    15  
    16  import (
    17  	"encoding/base64"
    18  	"io"
    19  	"sync"
    20  
    21  	jsoniter "github.com/json-iterator/go"
    22  )
    23  
    24  var jsonAPI = jsoniter.Config{
    25  	EscapeHTML:  false,
    26  	SortMapKeys: false,
    27  }.Froze()
    28  
    29  var jWriterPool = sync.Pool{
    30  	New: func() interface{} {
    31  		return &JSONWriter{}
    32  	},
    33  }
    34  
    35  // JSONWriter builds JSON in an efficient and structural way.
    36  //
    37  // Example Usage
    38  //
    39  //	w := BorrowJSONWriter(out)
    40  //
    41  //	w.WriteObject(func() {
    42  //	  w.WriteObjectField("payload", func() {
    43  //	    w.WriteObjectField("dml", func() {
    44  //	      w.WriteStringField("statement", "INSERT")
    45  //	      w.WriteUint64Field("ts", 100)
    46  //	    })
    47  //	  })
    48  //	  w.WriteObjectField("source", func() {
    49  //	    w.WriteStringField("source", "TiCDC")
    50  //	    w.WriteInt64Field("version", 1)
    51  //	  })
    52  //	})
    53  //
    54  //	ReturnJSONWriter(w)
    55  type JSONWriter struct {
    56  	out    io.Writer
    57  	stream *jsoniter.Stream // `stream` is created over `out`
    58  
    59  	needPrependComma bool
    60  }
    61  
    62  // BorrowJSONWriter borrows a JSONWriter instance from pool.
    63  // Remember to call ReturnJSONWriter to return the borrowed instance.
    64  func BorrowJSONWriter(out io.Writer) *JSONWriter {
    65  	w := jWriterPool.Get().(*JSONWriter)
    66  	w.out = out
    67  	w.stream = jsonAPI.BorrowStream(out)
    68  	w.needPrependComma = false
    69  	return w
    70  }
    71  
    72  // ReturnJSONWriter returns the borrowed JSONWriter instance to pool.
    73  func ReturnJSONWriter(w *JSONWriter) {
    74  	w.stream.Flush()
    75  	jsonAPI.ReturnStream(w.stream)
    76  	w.out = nil
    77  	w.stream = nil
    78  	jWriterPool.Put(w)
    79  }
    80  
    81  // Buffer returns the buffer if out is nil.
    82  // WARN: You may need to copy the result of the buffer. Otherwise the content of the buffer
    83  // may be changed.
    84  func (w *JSONWriter) Buffer() []byte {
    85  	return w.stream.Buffer()
    86  }
    87  
    88  // WriteRaw writes a raw string directly into the output.
    89  func (w *JSONWriter) WriteRaw(b string) {
    90  	w.stream.WriteRaw(b)
    91  }
    92  
    93  // WriteBase64String writes a base64 string like "<value>".
    94  func (w *JSONWriter) WriteBase64String(b []byte) {
    95  	if w.out == nil {
    96  		w.stream.WriteRaw(`"`)
    97  		encoder := base64.NewEncoder(base64.StdEncoding, w.stream)
    98  		_, _ = encoder.Write(b)
    99  		_ = encoder.Close()
   100  		w.stream.WriteRaw(`"`)
   101  	} else {
   102  		// If out is available, let's write to out directly to avoid extra copy.
   103  		// As we write to out directly so we need to flush the jsoniter stream first.
   104  		_ = w.stream.Flush()
   105  		_, _ = w.out.Write([]byte(`"`))
   106  		encoder := base64.NewEncoder(base64.StdEncoding, w.out)
   107  		_, _ = encoder.Write(b)
   108  		_ = encoder.Close()
   109  		_, _ = w.out.Write([]byte(`"`))
   110  	}
   111  }
   112  
   113  // WriteObject writes {......}.
   114  func (w *JSONWriter) WriteObject(objectFieldsWriteFn func()) {
   115  	lastNeedPrependComma := w.needPrependComma
   116  	w.needPrependComma = false
   117  	w.stream.WriteObjectStart()
   118  	objectFieldsWriteFn()
   119  	w.stream.WriteObjectEnd()
   120  	w.needPrependComma = lastNeedPrependComma
   121  }
   122  
   123  // WriteArray writes [......].
   124  func (w *JSONWriter) WriteArray(arrayElementsWriteFn func()) {
   125  	lastNeedPrependComma := w.needPrependComma
   126  	w.needPrependComma = false
   127  	w.stream.WriteArrayStart()
   128  	arrayElementsWriteFn()
   129  	w.stream.WriteArrayEnd()
   130  	w.needPrependComma = lastNeedPrependComma
   131  }
   132  
   133  // WriteBoolField writes a bool field like "<fieldName>":<value>.
   134  func (w *JSONWriter) WriteBoolField(fieldName string, value bool) {
   135  	if w.needPrependComma {
   136  		w.stream.WriteMore()
   137  	} else {
   138  		w.needPrependComma = true
   139  	}
   140  	w.stream.WriteObjectField(fieldName)
   141  	w.stream.WriteBool(value)
   142  }
   143  
   144  // WriteIntField writes a int field like "<fieldName>":<value>.
   145  func (w *JSONWriter) WriteIntField(fieldName string, value int) {
   146  	if w.needPrependComma {
   147  		w.stream.WriteMore()
   148  	} else {
   149  		w.needPrependComma = true
   150  	}
   151  	w.stream.WriteObjectField(fieldName)
   152  	w.stream.WriteInt(value)
   153  }
   154  
   155  // WriteInt64Field writes a int64 field like "<fieldName>":<value>.
   156  func (w *JSONWriter) WriteInt64Field(fieldName string, value int64) {
   157  	if w.needPrependComma {
   158  		w.stream.WriteMore()
   159  	} else {
   160  		w.needPrependComma = true
   161  	}
   162  	w.stream.WriteObjectField(fieldName)
   163  	w.stream.WriteInt64(value)
   164  }
   165  
   166  // WriteUint64Field writes a uint64 field like "<fieldName>":<value>.
   167  func (w *JSONWriter) WriteUint64Field(fieldName string, value uint64) {
   168  	if w.needPrependComma {
   169  		w.stream.WriteMore()
   170  	} else {
   171  		w.needPrependComma = true
   172  	}
   173  	w.stream.WriteObjectField(fieldName)
   174  	w.stream.WriteUint64(value)
   175  }
   176  
   177  // WriteFloat64Field writes a float64 field like "<fieldName>":<value>.
   178  func (w *JSONWriter) WriteFloat64Field(fieldName string, value float64) {
   179  	if w.needPrependComma {
   180  		w.stream.WriteMore()
   181  	} else {
   182  		w.needPrependComma = true
   183  	}
   184  	w.stream.WriteObjectField(fieldName)
   185  	w.stream.WriteFloat64(value)
   186  }
   187  
   188  // WriteStringField writes a string field like "<fieldName>":"<value>".
   189  func (w *JSONWriter) WriteStringField(fieldName string, value string) {
   190  	if w.needPrependComma {
   191  		w.stream.WriteMore()
   192  	} else {
   193  		w.needPrependComma = true
   194  	}
   195  	w.stream.WriteObjectField(fieldName)
   196  	w.stream.WriteString(value)
   197  }
   198  
   199  // WriteBase64StringField writes a base64 string field like "<fieldName>":"<value>".
   200  func (w *JSONWriter) WriteBase64StringField(fieldName string, b []byte) {
   201  	if w.needPrependComma {
   202  		w.stream.WriteMore()
   203  	} else {
   204  		w.needPrependComma = true
   205  	}
   206  	w.stream.WriteObjectField(fieldName)
   207  	w.WriteBase64String(b)
   208  }
   209  
   210  // WriteAnyField writes a field like "<fieldName>":<value>.
   211  func (w *JSONWriter) WriteAnyField(fieldName string, value any) {
   212  	if w.needPrependComma {
   213  		w.stream.WriteMore()
   214  	} else {
   215  		w.needPrependComma = true
   216  	}
   217  	w.stream.WriteObjectField(fieldName)
   218  	w.stream.WriteVal(value)
   219  }
   220  
   221  // WriteObjectField writes a object field like "<fieldName>":{......}.
   222  func (w *JSONWriter) WriteObjectField(fieldName string, objectFieldsWriteFn func()) {
   223  	if w.needPrependComma {
   224  		w.stream.WriteMore()
   225  	} else {
   226  		w.needPrependComma = true
   227  	}
   228  	w.stream.WriteObjectField(fieldName)
   229  	w.WriteObject(objectFieldsWriteFn)
   230  }
   231  
   232  // WriteArrayField writes a array field like "<fieldName>":[......].
   233  func (w *JSONWriter) WriteArrayField(fieldName string, arrayElementsWriteFn func()) {
   234  	if w.needPrependComma {
   235  		w.stream.WriteMore()
   236  	} else {
   237  		w.needPrependComma = true
   238  	}
   239  	w.stream.WriteObjectField(fieldName)
   240  	w.WriteArray(arrayElementsWriteFn)
   241  }
   242  
   243  // WriteNullField writes a null field like "<fieldName>":null.
   244  func (w *JSONWriter) WriteNullField(fieldName string) {
   245  	if w.needPrependComma {
   246  		w.stream.WriteMore()
   247  	} else {
   248  		w.needPrependComma = true
   249  	}
   250  	w.stream.WriteObjectField(fieldName)
   251  	w.stream.WriteNil()
   252  }
   253  
   254  // WriteBoolElement writes a bool array element like ,<value>.
   255  func (w *JSONWriter) WriteBoolElement(value bool) {
   256  	if w.needPrependComma {
   257  		w.stream.WriteMore()
   258  	} else {
   259  		w.needPrependComma = true
   260  	}
   261  	w.stream.WriteBool(value)
   262  }
   263  
   264  // WriteIntElement writes a int array element like ,<value>.
   265  func (w *JSONWriter) WriteIntElement(value int) {
   266  	if w.needPrependComma {
   267  		w.stream.WriteMore()
   268  	} else {
   269  		w.needPrependComma = true
   270  	}
   271  	w.stream.WriteInt(value)
   272  }
   273  
   274  // WriteInt64Element writes a int64 array element like ,<value>.
   275  func (w *JSONWriter) WriteInt64Element(value int64) {
   276  	if w.needPrependComma {
   277  		w.stream.WriteMore()
   278  	} else {
   279  		w.needPrependComma = true
   280  	}
   281  	w.stream.WriteInt64(value)
   282  }
   283  
   284  // WriteUint64Element writes a uint64 array element like ,<value>.
   285  func (w *JSONWriter) WriteUint64Element(value uint64) {
   286  	if w.needPrependComma {
   287  		w.stream.WriteMore()
   288  	} else {
   289  		w.needPrependComma = true
   290  	}
   291  	w.stream.WriteUint64(value)
   292  }
   293  
   294  // WriteFloat64Element writes a float64 array element like ,<value>.
   295  func (w *JSONWriter) WriteFloat64Element(value float64) {
   296  	if w.needPrependComma {
   297  		w.stream.WriteMore()
   298  	} else {
   299  		w.needPrependComma = true
   300  	}
   301  	w.stream.WriteFloat64(value)
   302  }
   303  
   304  // WriteStringElement writes a string array element like ,"<value>".
   305  func (w *JSONWriter) WriteStringElement(value string) {
   306  	if w.needPrependComma {
   307  		w.stream.WriteMore()
   308  	} else {
   309  		w.needPrependComma = true
   310  	}
   311  	w.stream.WriteString(value)
   312  }
   313  
   314  // WriteBase64StringElement writes a base64 string array element like ,"<value>".
   315  func (w *JSONWriter) WriteBase64StringElement(b []byte) {
   316  	if w.needPrependComma {
   317  		w.stream.WriteMore()
   318  	} else {
   319  		w.needPrependComma = true
   320  	}
   321  	w.WriteBase64String(b)
   322  }
   323  
   324  // WriteAnyElement writes a array element like ,<value>.
   325  func (w *JSONWriter) WriteAnyElement(value any) {
   326  	if w.needPrependComma {
   327  		w.stream.WriteMore()
   328  	} else {
   329  		w.needPrependComma = true
   330  	}
   331  	w.stream.WriteVal(value)
   332  }
   333  
   334  // WriteObjectElement writes a object array element like ,{......}.
   335  func (w *JSONWriter) WriteObjectElement(objectFieldsWriteFn func()) {
   336  	if w.needPrependComma {
   337  		w.stream.WriteMore()
   338  	} else {
   339  		w.needPrependComma = true
   340  	}
   341  	w.WriteObject(objectFieldsWriteFn)
   342  }
   343  
   344  // WriteArrayElement writes a array array element like ,[......].
   345  func (w *JSONWriter) WriteArrayElement(arrayElementsWriteFn func()) {
   346  	if w.needPrependComma {
   347  		w.stream.WriteMore()
   348  	} else {
   349  		w.needPrependComma = true
   350  	}
   351  	w.WriteArray(arrayElementsWriteFn)
   352  }
   353  
   354  // WriteNullElement writes a null array element like ,null.
   355  func (w *JSONWriter) WriteNullElement() {
   356  	if w.needPrependComma {
   357  		w.stream.WriteMore()
   358  	} else {
   359  		w.needPrependComma = true
   360  	}
   361  	w.stream.WriteNil()
   362  }