go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/llx/rawdata_json.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package llx 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "math" 12 "sort" 13 "strconv" 14 "time" 15 16 "go.mondoo.com/cnquery/types" 17 "go.mondoo.com/cnquery/utils/sortx" 18 ) 19 20 func intKeys(m map[int]interface{}) []int { 21 keys := make([]int, len(m)) 22 var i int 23 for k := range m { 24 keys[i] = k 25 i++ 26 } 27 return keys 28 } 29 30 // Note: We override the default output here to enable JSON5 like export of infinity. 31 func int2json(i int64) string { 32 if i == math.MaxInt64 { 33 return "Inf" 34 } 35 if i == math.MinInt64 { 36 return "-Inf" 37 } 38 39 return strconv.FormatInt(i, 10) 40 } 41 42 // Note: We override the default output here to enable JSON5 like export of infinity. 43 func float2json(f float64) string { 44 if math.IsInf(f, 1) { 45 return "Inf" 46 } 47 if math.IsInf(f, -1) { 48 return "-Inf" 49 } 50 51 return strconv.FormatFloat(f, 'g', -1, 64) 52 } 53 54 // Takes care of escaping the given string 55 func string2json(s string) string { 56 return fmt.Sprintf("%#v", s) 57 } 58 59 func label(ref string, bundle *CodeBundle, isResource bool) string { 60 if bundle == nil { 61 return "<unknown>" 62 } 63 64 labels := bundle.Labels 65 if labels == nil { 66 return ref 67 } 68 69 label := labels.Labels[ref] 70 if label == "" { 71 return "<unknown>" 72 } 73 74 return label 75 } 76 77 func removeUnderscoreKeys(keys []string) []string { 78 results := make([]string, 0, len(keys)) 79 for i := 0; i < len(keys); i++ { 80 if keys[i] != "" && keys[i][0] != '_' { 81 results = append(results, keys[i]) 82 } 83 } 84 return results 85 } 86 87 func refMapJSON(typ types.Type, data map[string]interface{}, codeID string, bundle *CodeBundle, buf *bytes.Buffer) error { 88 buf.WriteByte('{') 89 90 keys := sortx.Keys(data) 91 92 // What is the best explanation for why we do this? 93 keys = removeUnderscoreKeys(keys) 94 95 last := len(keys) - 1 96 for i, k := range keys { 97 v := data[k] 98 label := label(k, bundle, true) 99 buf.WriteString(string2json(label)) 100 buf.WriteString(":") 101 102 val := v.(*RawData) 103 if val.Error != nil { 104 buf.WriteString(PrettyPrintString("Error: " + val.Error.Error())) 105 } else { 106 rawDataJSON(val.Type, val.Value, k, bundle, buf) 107 } 108 109 if i != last { 110 buf.WriteByte(',') 111 } 112 } 113 114 buf.WriteByte('}') 115 return nil 116 } 117 118 func rawDictJSON(typ types.Type, raw interface{}, buf *bytes.Buffer) error { 119 switch data := raw.(type) { 120 case bool: 121 if data { 122 buf.WriteString("true") 123 } else { 124 buf.WriteString("false") 125 } 126 return nil 127 128 case int64: 129 buf.WriteString(int2json(data)) 130 return nil 131 132 case float64: 133 buf.WriteString(float2json(data)) 134 return nil 135 136 case string: 137 buf.WriteString(string2json(data)) 138 return nil 139 140 case time.Time: 141 b, err := data.MarshalJSON() 142 buf.Write(b) 143 return err 144 145 case []interface{}: 146 buf.WriteByte('[') 147 148 last := len(data) - 1 149 for i := range data { 150 err := rawDictJSON(typ, data[i], buf) 151 if err != nil { 152 return err 153 } 154 if i != last { 155 buf.WriteByte(',') 156 } 157 } 158 159 buf.WriteByte(']') 160 return nil 161 162 case map[string]interface{}: 163 buf.WriteByte('{') 164 165 keys := sortx.Keys(data) 166 167 last := len(keys) - 1 168 for i, k := range keys { 169 v := data[k] 170 buf.WriteString(string2json(k) + ":") 171 172 if v == nil { 173 buf.WriteString("null") 174 } else { 175 err := rawDictJSON(typ, v, buf) 176 if err != nil { 177 return err 178 } 179 } 180 181 if i != last { 182 buf.WriteByte(',') 183 } 184 } 185 186 buf.WriteByte('}') 187 return nil 188 189 default: 190 b, err := json.Marshal(raw) 191 buf.Write(b) 192 return err 193 } 194 } 195 196 func rawArrayJSON(typ types.Type, data []interface{}, codeID string, bundle *CodeBundle, buf *bytes.Buffer) error { 197 buf.WriteByte('[') 198 199 last := len(data) - 1 200 childType := typ.Child() 201 var err error 202 for i := range data { 203 err = rawDataJSON(childType, data[i], codeID, bundle, buf) 204 if err != nil { 205 return err 206 } 207 208 if i != last { 209 buf.WriteByte(',') 210 } 211 } 212 213 buf.WriteByte(']') 214 215 return nil 216 } 217 218 func rawStringMapJSON(typ types.Type, data map[string]interface{}, codeID string, bundle *CodeBundle, buf *bytes.Buffer) error { 219 buf.WriteByte('{') 220 221 last := len(data) - 1 222 childType := typ.Child() 223 keys := sortx.Keys(data) 224 225 var err error 226 for i, key := range keys { 227 buf.WriteString(string2json(key) + ":") 228 229 err = rawDataJSON(childType, data[key], codeID, bundle, buf) 230 if err != nil { 231 return err 232 } 233 234 if i != last { 235 buf.WriteByte(',') 236 } 237 } 238 239 buf.WriteByte('}') 240 241 return nil 242 } 243 244 func rawIntMapJSON(typ types.Type, data map[int]interface{}, codeID string, bundle *CodeBundle, buf *bytes.Buffer) error { 245 buf.WriteByte('{') 246 247 last := len(data) - 1 248 childType := typ.Child() 249 250 keys := intKeys(data) 251 sort.Ints(keys) 252 253 var err error 254 for i, key := range keys { 255 buf.WriteString(string2json(strconv.Itoa(key)) + ":") 256 257 err = rawDataJSON(childType, data[key], codeID, bundle, buf) 258 if err != nil { 259 return err 260 } 261 262 if i != last { 263 buf.WriteByte(',') 264 } 265 } 266 267 buf.WriteByte('}') 268 269 return nil 270 } 271 272 // The heart of the JSON marshaller. We try to avoid the default marshaller whenever 273 // possible for now, because our type system provides most of the information we need, 274 // allowing us to avoid more costly reflection calls. 275 func rawDataJSON(typ types.Type, data interface{}, codeID string, bundle *CodeBundle, buf *bytes.Buffer) error { 276 if typ.NotSet() { 277 return errors.New("type information is missing") 278 } 279 280 if data == nil { 281 buf.WriteString("null") 282 return nil 283 } 284 285 switch typ.Underlying() { 286 case types.Any: 287 r, err := json.Marshal(data) 288 buf.Write(r) 289 return err 290 291 case types.Ref: 292 r := "\"ref:" + fmt.Sprintf("%d", data.(int32)) + "\"" 293 buf.WriteString(r) 294 return nil 295 296 case types.Nil: 297 buf.WriteString("null") 298 return nil 299 300 case types.Bool: 301 if data.(bool) { 302 buf.WriteString("true") 303 } else { 304 buf.WriteString("false") 305 } 306 return nil 307 308 case types.Int: 309 buf.WriteString(int2json(data.(int64))) 310 return nil 311 312 case types.Float: 313 // Note: We override the default output here to enable JSON5 like export of infinity. 314 if math.IsInf(data.(float64), 1) { 315 buf.WriteString("Inf") 316 return nil 317 } 318 if math.IsInf(data.(float64), -1) { 319 buf.WriteString("-Inf") 320 return nil 321 } 322 323 buf.WriteString(strconv.FormatFloat(data.(float64), 'g', -1, 64)) 324 return nil 325 326 case types.String: 327 buf.WriteString(string2json(data.(string))) 328 return nil 329 330 case types.Regex: 331 raw := string2json(data.(string)) 332 buf.WriteByte(raw[0]) 333 buf.WriteByte('/') 334 buf.WriteString(raw[1 : len(raw)-1]) 335 buf.WriteByte('/') 336 buf.WriteByte(raw[len(raw)-1]) 337 return nil 338 339 case types.Time: 340 time := data.(*time.Time) 341 if time == nil { 342 buf.WriteString("null") 343 return nil 344 } 345 346 // if *time == NeverPastTime || *time == NeverFutureTime { 347 // TODO: ... unclear 348 // } 349 350 b, err := time.MarshalJSON() 351 buf.Write(b) 352 return err 353 354 case types.Dict: 355 return rawDictJSON(typ, data, buf) 356 357 case types.Score: 358 buf.WriteString(ScoreString(data.([]byte))) 359 return nil 360 361 case types.Block: 362 return refMapJSON(typ, data.(map[string]interface{}), codeID, bundle, buf) 363 364 case types.ArrayLike: 365 return rawArrayJSON(typ, data.([]interface{}), codeID, bundle, buf) 366 367 case types.MapLike: 368 if typ.Key() == types.String { 369 return rawStringMapJSON(typ, data.(map[string]interface{}), codeID, bundle, buf) 370 } 371 if typ.Key() == types.Int { 372 return rawIntMapJSON(typ, data.(map[int]interface{}), codeID, bundle, buf) 373 } 374 return errors.New("unable to marshal map, its type is not supported: " + typ.Label() + ", raw: " + fmt.Sprintf("%#v", data)) 375 376 case types.ResourceLike: 377 r := data.(Resource) 378 idline := r.MqlName() 379 if id := r.MqlID(); id != "" { 380 idline += " id = " + id 381 } 382 383 buf.WriteString(string2json(idline)) 384 return nil 385 386 default: 387 b, err := json.Marshal(data) 388 buf.Write(b) 389 return err 390 } 391 } 392 393 func JSONerror(err error) []byte { 394 return []byte("{\"error\":" + string2json(err.Error()) + "}") 395 } 396 397 func (r *RawData) JSON(codeID string, bundle *CodeBundle) []byte { 398 if r.Value == nil && r.Error != nil { 399 return JSONerror(r.Error) 400 } 401 402 var res bytes.Buffer 403 rawDataJSON(r.Type, r.Value, codeID, bundle, &res) 404 return res.Bytes() 405 } 406 407 func (r *RawData) JSONfield(codeID string, bundle *CodeBundle) []byte { 408 label := label(codeID, bundle, true) 409 value := r.JSON(codeID, bundle) 410 return []byte(string2json(label) + ":" + string(value)) 411 }