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 }