github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/columns/formatter/json/json.go (about) 1 // Copyright 2023 The Inspektor Gadget authors 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package json 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "math" 21 "reflect" 22 "strconv" 23 "strings" 24 "unicode/utf8" 25 _ "unsafe" 26 27 "github.com/inspektor-gadget/inspektor-gadget/pkg/columns" 28 ) 29 30 type column[T any] struct { 31 column *columns.Column[T] 32 formatter func(*encodeState, *T) 33 } 34 35 type Formatter[T any] struct { 36 options *Options 37 columns []*column[T] 38 printer func(buf *encodeState, entry *T, indent string) 39 } 40 41 // NewFormatter returns a Formatter that will turn entries of type T into JSON representation 42 func NewFormatter[T any](cols columns.ColumnMap[T], options ...Option) *Formatter[T] { 43 opts := DefaultOptions() 44 for _, o := range options { 45 o(opts) 46 } 47 48 ncols := make([]*column[T], 0) 49 for _, col := range cols.GetOrderedColumns() { 50 colName := col.Name 51 if strings.Contains(colName, ".") { 52 hierarchy := strings.Split(colName, ".") 53 colName = hierarchy[len(hierarchy)-1] 54 } 55 name, _ := json.Marshal(colName) 56 key := append(name, []byte(": ")...) 57 58 var formatter func(*encodeState, *T) 59 switch col.Kind() { 60 default: 61 continue 62 case reflect.Int, 63 reflect.Int8, 64 reflect.Int16, 65 reflect.Int32, 66 reflect.Int64: 67 ff := columns.GetFieldAsNumberFunc[int64, T](col) 68 formatter = func(e *encodeState, t *T) { 69 e.Write(key) 70 b := strconv.AppendInt(e.scratch[:0], ff(t), 10) 71 e.Write(b) 72 } 73 case reflect.Uint, 74 reflect.Uint8, 75 reflect.Uint16, 76 reflect.Uint32, 77 reflect.Uint64: 78 ff := columns.GetFieldAsNumberFunc[uint64, T](col) 79 formatter = func(e *encodeState, t *T) { 80 e.Write(key) 81 b := strconv.AppendUint(e.scratch[:0], ff(t), 10) 82 e.Write(b) 83 } 84 case reflect.Bool: 85 ff := columns.GetFieldFunc[bool, T](col) 86 formatter = func(e *encodeState, t *T) { 87 e.Write(key) 88 if ff(t) { 89 e.WriteString("true") 90 return 91 } 92 e.WriteString("false") 93 } 94 case reflect.Float32: 95 ff := columns.GetFieldAsNumberFunc[float64, T](col) 96 formatter = func(e *encodeState, t *T) { 97 e.Write(key) 98 floatEncoder(32).writeFloat(e, ff(t)) 99 } 100 case reflect.Float64: 101 ff := columns.GetFieldAsNumberFunc[float64, T](col) 102 formatter = func(e *encodeState, t *T) { 103 e.Write(key) 104 floatEncoder(64).writeFloat(e, ff(t)) 105 } 106 case reflect.Array: 107 ff := columns.GetFieldAsString[T](col) 108 formatter = func(e *encodeState, t *T) { 109 e.Write(key) 110 writeString(e, ff(t)) 111 } 112 case reflect.String: 113 ff := columns.GetFieldFunc[string, T](col) 114 formatter = func(e *encodeState, t *T) { 115 e.Write(key) 116 writeString(e, ff(t)) 117 } 118 } 119 120 ncols = append(ncols, &column[T]{ 121 column: col, 122 formatter: formatter, 123 }) 124 } 125 126 tf := &Formatter[T]{ 127 options: opts, 128 columns: ncols, 129 } 130 tf.printer = tf.getPrinter(0, ncols) 131 return tf 132 } 133 134 func (f *Formatter[T]) getPrinter(level int, cols []*column[T]) func(buf *encodeState, entry *T, indent string) { 135 levelIndent := []byte(" ") 136 colsWithParent := map[string][]*column[T]{} 137 colsWithoutParent := []*column[T]{} 138 139 funcs := make([]func(buf *encodeState, entry *T, indent string), 0) 140 141 for _, col := range cols { 142 // Save columns which have a parent in its name 143 if strings.Count(col.column.Name, ".") > level { 144 hierarchy := strings.Split(col.column.Name, ".") 145 parentName := hierarchy[level] 146 colsWithParent[parentName] = append(colsWithParent[parentName], col) 147 continue 148 } 149 // Don't print columns without a parent directly 150 // It is possible that their name is the same as a name of an object in the same level 151 // For example: {"a": {"bbb": 1}, "a": 2} 152 // We will filter them out after we gathered all object names 153 colsWithoutParent = append(colsWithoutParent, col) 154 } 155 156 first := true 157 // Better name? 158 handlePrefix := func() { 159 if !first { 160 funcs = append(funcs, func(buf *encodeState, entry *T, indent string) { 161 buf.WriteByte(',') 162 if f.options.prettyPrint { 163 buf.WriteByte('\n') 164 buf.WriteString(indent) 165 buf.Write(levelIndent) 166 } else { 167 buf.WriteByte(' ') 168 } 169 }) 170 } else { 171 if f.options.prettyPrint { 172 funcs = append(funcs, func(buf *encodeState, entry *T, indent string) { 173 buf.WriteByte('\n') 174 buf.WriteString(indent) 175 buf.Write(levelIndent) 176 }) 177 } 178 first = false 179 } 180 } 181 182 // Now print columns with their parents 183 for key, val := range colsWithParent { 184 handlePrefix() 185 186 parentName, _ := json.Marshal(strings.Split(key, ".")[0]) 187 188 childFuncs := f.getPrinter(level+1, val) 189 funcs = append(funcs, func(buf *encodeState, entry *T, indent string) { 190 buf.Write(parentName) 191 buf.WriteString(": {") 192 193 childFuncs(buf, entry, indent+" ") 194 195 // End parent 196 if f.options.prettyPrint { 197 buf.WriteString("\n" + indent + " }") 198 } else { 199 buf.WriteByte('}') 200 } 201 }) 202 } 203 204 // Now all cols without a parent 205 for i := range colsWithoutParent { 206 col := colsWithoutParent[i] 207 childName := strings.Split(col.column.Name, ".")[level] 208 _, found := colsWithParent[childName] 209 if found { 210 // We already have an object with this key, skip this column 211 continue 212 } 213 handlePrefix() 214 215 funcs = append(funcs, func(buf *encodeState, entry *T, indent string) { 216 col.formatter(buf, entry) 217 }) 218 } 219 220 return func(buf *encodeState, entry *T, indent string) { 221 for _, f := range funcs { 222 f(buf, entry, indent) 223 } 224 } 225 } 226 227 func (f *Formatter[T]) formatEntry(buf *encodeState, entry *T, indent string) { 228 if entry == nil { 229 buf.WriteString(indent + "null") 230 return 231 } 232 buf.WriteString(indent + "{") 233 f.printer(buf, entry, indent) 234 if f.options.prettyPrint { 235 buf.WriteString("\n" + indent) 236 } 237 buf.WriteByte('}') 238 } 239 240 // FormatEntry returns an entry as a formatted string, respecting the given formatting settings 241 func (f *Formatter[T]) FormatEntry(entry *T) string { 242 buf := bufpool.Get().(*encodeState) 243 buf.Reset() 244 defer bufpool.Put(buf) 245 246 f.formatEntry(buf, entry, "") 247 248 return buf.String() 249 } 250 251 // FormatEntries returns a slice of entries as a formatted string, respecting the given formatting settings 252 func (f *Formatter[T]) FormatEntries(entries []*T) string { 253 if entries == nil { 254 return "null" 255 } 256 257 // TODO[Mauricio]: I can't remember why but the behavior of the default golang formatter was 258 // causing issues when the slice was empty. So let's marshall to [] instead of null on this case. 259 if len(entries) == 0 { 260 return "[]" 261 } 262 263 buf := bufpool.Get().(*encodeState) 264 buf.Reset() 265 defer bufpool.Put(buf) 266 267 if !f.options.prettyPrint { 268 buf.WriteByte('[') 269 for l, entry := range entries { 270 f.formatEntry(buf, entry, "") 271 if l < len(entries)-1 { 272 buf.WriteString(", ") 273 } 274 } 275 buf.WriteByte(']') 276 } else { 277 buf.WriteString("[\n") 278 for l, entry := range entries { 279 f.formatEntry(buf, entry, " ") 280 if l < len(entries)-1 { 281 buf.WriteString(",\n") 282 } 283 } 284 buf.WriteString("\n]") 285 } 286 287 return buf.String() 288 } 289 290 type floatEncoder int // number of bits 291 292 // from encoding/json/encode.go 293 func (bits floatEncoder) writeFloat(e *encodeState, f float64) { 294 if math.IsInf(f, 0) || math.IsNaN(f) { 295 e.err = fmt.Errorf("invalid float value") 296 return 297 } 298 299 // Convert as if by ES6 number to string conversion. 300 // This matches most other JSON generators. 301 // See golang.org/issue/6384 and golang.org/issue/14135. 302 // Like fmt %g, but the exponent cutoffs are different 303 // and exponents themselves are not padded to two digits. 304 b := e.scratch[:0] 305 abs := math.Abs(f) 306 fmt := byte('f') 307 // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. 308 if abs != 0 { 309 if bits == 64 && (abs < 1e-6 || abs >= 1e21) || bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) { 310 fmt = 'e' 311 } 312 } 313 b = strconv.AppendFloat(b, f, fmt, -1, int(bits)) 314 if fmt == 'e' { 315 // clean up e-09 to e-9 316 n := len(b) 317 if n >= 4 && b[n-4] == 'e' && b[n-3] == '-' && b[n-2] == '0' { 318 b[n-2] = b[n-1] 319 b = b[:n-1] 320 } 321 } 322 323 e.Write(b) 324 } 325 326 // from encoding/json/encode.go 327 func writeString(e *encodeState, s string) { 328 e.WriteByte('"') 329 start := 0 330 for i := 0; i < len(s); { 331 if b := s[i]; b < utf8.RuneSelf { 332 if safeSet[b] { 333 i++ 334 continue 335 } 336 if start < i { 337 e.WriteString(s[start:i]) 338 } 339 e.WriteByte('\\') 340 switch b { 341 case '\\', '"': 342 e.WriteByte(b) 343 case '\n': 344 e.WriteByte('n') 345 case '\r': 346 e.WriteByte('r') 347 case '\t': 348 e.WriteByte('t') 349 default: 350 // This encodes bytes < 0x20 except for \t, \n and \r. 351 // If escapeHTML is set, it also escapes <, >, and & 352 // because they can lead to security holes when 353 // user-controlled strings are rendered into JSON 354 // and served to some browsers. 355 e.WriteString(`u00`) 356 e.WriteByte(hex[b>>4]) 357 e.WriteByte(hex[b&0xF]) 358 } 359 i++ 360 start = i 361 continue 362 } 363 c, size := utf8.DecodeRuneInString(s[i:]) 364 if c == utf8.RuneError && size == 1 { 365 if start < i { 366 e.WriteString(s[start:i]) 367 } 368 e.WriteString(`\ufffd`) 369 i += size 370 start = i 371 continue 372 } 373 // U+2028 is LINE SEPARATOR. 374 // U+2029 is PARAGRAPH SEPARATOR. 375 // They are both technically valid characters in JSON strings, 376 // but don't work in JSONP, which has to be evaluated as JavaScript, 377 // and can lead to security holes there. It is valid JSON to 378 // escape them, so we do so unconditionally. 379 // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. 380 if c == '\u2028' || c == '\u2029' { 381 if start < i { 382 e.WriteString(s[start:i]) 383 } 384 e.WriteString(`\u202`) 385 e.WriteByte(hex[c&0xF]) 386 i += size 387 start = i 388 continue 389 } 390 i += size 391 } 392 if start < len(s) { 393 e.WriteString(s[start:]) 394 } 395 e.WriteByte('"') 396 } 397 398 var hex = "0123456789abcdef" 399 400 // use safeSet from encoding/json directly 401 // 402 //go:linkname safeSet encoding/json.safeSet 403 var safeSet = [utf8.RuneSelf]bool{}