github.com/gogf/gf/v2@v2.7.4/util/gutil/gutil_dump.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gutil 8 9 import ( 10 "bytes" 11 "encoding/json" 12 "fmt" 13 "io" 14 "reflect" 15 "strings" 16 17 "github.com/gogf/gf/v2/internal/reflection" 18 "github.com/gogf/gf/v2/os/gstructs" 19 "github.com/gogf/gf/v2/text/gstr" 20 ) 21 22 // iString is used for type assert api for String(). 23 type iString interface { 24 String() string 25 } 26 27 // iError is used for type assert api for Error(). 28 type iError interface { 29 Error() string 30 } 31 32 // iMarshalJSON is the interface for custom Json marshaling. 33 type iMarshalJSON interface { 34 MarshalJSON() ([]byte, error) 35 } 36 37 // DumpOption specifies the behavior of function Export. 38 type DumpOption struct { 39 WithType bool // WithType specifies dumping content with type information. 40 ExportedOnly bool // Only dump Exported fields for structs. 41 } 42 43 // Dump prints variables `values` to stdout with more manually readable. 44 func Dump(values ...interface{}) { 45 for _, value := range values { 46 DumpWithOption(value, DumpOption{ 47 WithType: false, 48 ExportedOnly: false, 49 }) 50 } 51 } 52 53 // DumpWithType acts like Dump, but with type information. 54 // Also see Dump. 55 func DumpWithType(values ...interface{}) { 56 for _, value := range values { 57 DumpWithOption(value, DumpOption{ 58 WithType: true, 59 ExportedOnly: false, 60 }) 61 } 62 } 63 64 // DumpWithOption returns variables `values` as a string with more manually readable. 65 func DumpWithOption(value interface{}, option DumpOption) { 66 buffer := bytes.NewBuffer(nil) 67 DumpTo(buffer, value, DumpOption{ 68 WithType: option.WithType, 69 ExportedOnly: option.ExportedOnly, 70 }) 71 fmt.Println(buffer.String()) 72 } 73 74 // DumpTo writes variables `values` as a string in to `writer` with more manually readable 75 func DumpTo(writer io.Writer, value interface{}, option DumpOption) { 76 buffer := bytes.NewBuffer(nil) 77 doDump(value, "", buffer, doDumpOption{ 78 WithType: option.WithType, 79 ExportedOnly: option.ExportedOnly, 80 }) 81 _, _ = writer.Write(buffer.Bytes()) 82 } 83 84 type doDumpOption struct { 85 WithType bool 86 ExportedOnly bool 87 DumpedPointerSet map[string]struct{} 88 } 89 90 func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDumpOption) { 91 if option.DumpedPointerSet == nil { 92 option.DumpedPointerSet = map[string]struct{}{} 93 } 94 95 if value == nil { 96 buffer.WriteString(`<nil>`) 97 return 98 } 99 var reflectValue reflect.Value 100 if v, ok := value.(reflect.Value); ok { 101 reflectValue = v 102 if v.IsValid() && v.CanInterface() { 103 value = v.Interface() 104 } else { 105 if convertedValue, ok := reflection.ValueToInterface(v); ok { 106 value = convertedValue 107 } 108 } 109 } else { 110 reflectValue = reflect.ValueOf(value) 111 } 112 var reflectKind = reflectValue.Kind() 113 // Double check nil value. 114 if value == nil || reflectKind == reflect.Invalid { 115 buffer.WriteString(`<nil>`) 116 return 117 } 118 var ( 119 reflectTypeName = reflectValue.Type().String() 120 ptrAddress string 121 newIndent = indent + dumpIndent 122 ) 123 reflectTypeName = strings.ReplaceAll(reflectTypeName, `[]uint8`, `[]byte`) 124 for reflectKind == reflect.Ptr { 125 if ptrAddress == "" { 126 ptrAddress = fmt.Sprintf(`0x%x`, reflectValue.Pointer()) 127 } 128 reflectValue = reflectValue.Elem() 129 reflectKind = reflectValue.Kind() 130 } 131 var ( 132 exportInternalInput = doDumpInternalInput{ 133 Value: value, 134 Indent: indent, 135 NewIndent: newIndent, 136 Buffer: buffer, 137 Option: option, 138 PtrAddress: ptrAddress, 139 ReflectValue: reflectValue, 140 ReflectTypeName: reflectTypeName, 141 ExportedOnly: option.ExportedOnly, 142 DumpedPointerSet: option.DumpedPointerSet, 143 } 144 ) 145 switch reflectKind { 146 case reflect.Slice, reflect.Array: 147 doDumpSlice(exportInternalInput) 148 149 case reflect.Map: 150 doDumpMap(exportInternalInput) 151 152 case reflect.Struct: 153 doDumpStruct(exportInternalInput) 154 155 case reflect.String: 156 doDumpString(exportInternalInput) 157 158 case reflect.Bool: 159 doDumpBool(exportInternalInput) 160 161 case 162 reflect.Int, 163 reflect.Int8, 164 reflect.Int16, 165 reflect.Int32, 166 reflect.Int64, 167 reflect.Uint, 168 reflect.Uint8, 169 reflect.Uint16, 170 reflect.Uint32, 171 reflect.Uint64, 172 reflect.Float32, 173 reflect.Float64, 174 reflect.Complex64, 175 reflect.Complex128: 176 doDumpNumber(exportInternalInput) 177 178 case reflect.Chan: 179 buffer.WriteString(fmt.Sprintf(`<%s>`, reflectValue.Type().String())) 180 181 case reflect.Func: 182 if reflectValue.IsNil() || !reflectValue.IsValid() { 183 buffer.WriteString(`<nil>`) 184 } else { 185 buffer.WriteString(fmt.Sprintf(`<%s>`, reflectValue.Type().String())) 186 } 187 188 case reflect.Interface: 189 doDump(exportInternalInput.ReflectValue.Elem(), indent, buffer, option) 190 191 default: 192 doDumpDefault(exportInternalInput) 193 } 194 } 195 196 type doDumpInternalInput struct { 197 Value interface{} 198 Indent string 199 NewIndent string 200 Buffer *bytes.Buffer 201 Option doDumpOption 202 ReflectValue reflect.Value 203 ReflectTypeName string 204 PtrAddress string 205 ExportedOnly bool 206 DumpedPointerSet map[string]struct{} 207 } 208 209 func doDumpSlice(in doDumpInternalInput) { 210 if b, ok := in.Value.([]byte); ok { 211 if !in.Option.WithType { 212 in.Buffer.WriteString(fmt.Sprintf(`"%s"`, addSlashesForString(string(b)))) 213 } else { 214 in.Buffer.WriteString(fmt.Sprintf( 215 `%s(%d) "%s"`, 216 in.ReflectTypeName, 217 len(string(b)), 218 string(b), 219 )) 220 } 221 return 222 } 223 if in.ReflectValue.Len() == 0 { 224 if !in.Option.WithType { 225 in.Buffer.WriteString("[]") 226 } else { 227 in.Buffer.WriteString(fmt.Sprintf("%s(0) []", in.ReflectTypeName)) 228 } 229 return 230 } 231 if !in.Option.WithType { 232 in.Buffer.WriteString("[\n") 233 } else { 234 in.Buffer.WriteString(fmt.Sprintf("%s(%d) [\n", in.ReflectTypeName, in.ReflectValue.Len())) 235 } 236 for i := 0; i < in.ReflectValue.Len(); i++ { 237 in.Buffer.WriteString(in.NewIndent) 238 doDump(in.ReflectValue.Index(i), in.NewIndent, in.Buffer, in.Option) 239 in.Buffer.WriteString(",\n") 240 } 241 in.Buffer.WriteString(fmt.Sprintf("%s]", in.Indent)) 242 } 243 244 func doDumpMap(in doDumpInternalInput) { 245 var mapKeys = make([]reflect.Value, 0) 246 for _, key := range in.ReflectValue.MapKeys() { 247 if !key.CanInterface() { 248 continue 249 } 250 mapKey := key 251 mapKeys = append(mapKeys, mapKey) 252 } 253 if len(mapKeys) == 0 { 254 if !in.Option.WithType { 255 in.Buffer.WriteString("{}") 256 } else { 257 in.Buffer.WriteString(fmt.Sprintf("%s(0) {}", in.ReflectTypeName)) 258 } 259 return 260 } 261 var ( 262 maxSpaceNum = 0 263 tmpSpaceNum = 0 264 mapKeyStr = "" 265 ) 266 for _, key := range mapKeys { 267 tmpSpaceNum = len(fmt.Sprintf(`%v`, key.Interface())) 268 if tmpSpaceNum > maxSpaceNum { 269 maxSpaceNum = tmpSpaceNum 270 } 271 } 272 if !in.Option.WithType { 273 in.Buffer.WriteString("{\n") 274 } else { 275 in.Buffer.WriteString(fmt.Sprintf("%s(%d) {\n", in.ReflectTypeName, len(mapKeys))) 276 } 277 for _, mapKey := range mapKeys { 278 tmpSpaceNum = len(fmt.Sprintf(`%v`, mapKey.Interface())) 279 if mapKey.Kind() == reflect.String { 280 mapKeyStr = fmt.Sprintf(`"%v"`, mapKey.Interface()) 281 } else { 282 mapKeyStr = fmt.Sprintf(`%v`, mapKey.Interface()) 283 } 284 // Map key and indent string dump. 285 if !in.Option.WithType { 286 in.Buffer.WriteString(fmt.Sprintf( 287 "%s%v:%s", 288 in.NewIndent, 289 mapKeyStr, 290 strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1), 291 )) 292 } else { 293 in.Buffer.WriteString(fmt.Sprintf( 294 "%s%s(%v):%s", 295 in.NewIndent, 296 mapKey.Type().String(), 297 mapKeyStr, 298 strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1), 299 )) 300 } 301 // Map value dump. 302 doDump(in.ReflectValue.MapIndex(mapKey), in.NewIndent, in.Buffer, in.Option) 303 in.Buffer.WriteString(",\n") 304 } 305 in.Buffer.WriteString(fmt.Sprintf("%s}", in.Indent)) 306 } 307 308 func doDumpStruct(in doDumpInternalInput) { 309 if in.PtrAddress != "" { 310 if _, ok := in.DumpedPointerSet[in.PtrAddress]; ok { 311 in.Buffer.WriteString(fmt.Sprintf(`<cycle dump %s>`, in.PtrAddress)) 312 return 313 } 314 } 315 in.DumpedPointerSet[in.PtrAddress] = struct{}{} 316 317 structFields, _ := gstructs.Fields(gstructs.FieldsInput{ 318 Pointer: in.Value, 319 RecursiveOption: gstructs.RecursiveOptionEmbedded, 320 }) 321 var ( 322 hasNoExportedFields = true 323 _, isReflectValue = in.Value.(reflect.Value) 324 ) 325 for _, field := range structFields { 326 if field.IsExported() { 327 hasNoExportedFields = false 328 break 329 } 330 } 331 if !isReflectValue && (len(structFields) == 0 || hasNoExportedFields) { 332 var ( 333 structContentStr = "" 334 attributeCountStr = "0" 335 ) 336 if v, ok := in.Value.(iString); ok { 337 structContentStr = v.String() 338 } else if v, ok := in.Value.(iError); ok { 339 structContentStr = v.Error() 340 } else if v, ok := in.Value.(iMarshalJSON); ok { 341 b, _ := v.MarshalJSON() 342 structContentStr = string(b) 343 } else { 344 // Has no common interface implements. 345 if len(structFields) != 0 { 346 goto dumpStructFields 347 } 348 } 349 if structContentStr == "" { 350 structContentStr = "{}" 351 } else { 352 structContentStr = fmt.Sprintf(`"%s"`, addSlashesForString(structContentStr)) 353 attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2) 354 } 355 if !in.Option.WithType { 356 in.Buffer.WriteString(structContentStr) 357 } else { 358 in.Buffer.WriteString(fmt.Sprintf( 359 "%s(%s) %s", 360 in.ReflectTypeName, 361 attributeCountStr, 362 structContentStr, 363 )) 364 } 365 return 366 } 367 368 dumpStructFields: 369 var ( 370 maxSpaceNum = 0 371 tmpSpaceNum = 0 372 ) 373 for _, field := range structFields { 374 if in.ExportedOnly && !field.IsExported() { 375 continue 376 } 377 tmpSpaceNum = len(field.Name()) 378 if tmpSpaceNum > maxSpaceNum { 379 maxSpaceNum = tmpSpaceNum 380 } 381 } 382 if !in.Option.WithType { 383 in.Buffer.WriteString("{\n") 384 } else { 385 in.Buffer.WriteString(fmt.Sprintf("%s(%d) {\n", in.ReflectTypeName, len(structFields))) 386 } 387 for _, field := range structFields { 388 if in.ExportedOnly && !field.IsExported() { 389 continue 390 } 391 tmpSpaceNum = len(fmt.Sprintf(`%v`, field.Name())) 392 in.Buffer.WriteString(fmt.Sprintf( 393 "%s%s:%s", 394 in.NewIndent, 395 field.Name(), 396 strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1), 397 )) 398 doDump(field.Value, in.NewIndent, in.Buffer, in.Option) 399 in.Buffer.WriteString(",\n") 400 } 401 in.Buffer.WriteString(fmt.Sprintf("%s}", in.Indent)) 402 } 403 404 func doDumpNumber(in doDumpInternalInput) { 405 if v, ok := in.Value.(iString); ok { 406 s := v.String() 407 if !in.Option.WithType { 408 in.Buffer.WriteString(fmt.Sprintf(`"%v"`, addSlashesForString(s))) 409 } else { 410 in.Buffer.WriteString(fmt.Sprintf( 411 `%s(%d) "%v"`, 412 in.ReflectTypeName, 413 len(s), 414 addSlashesForString(s), 415 )) 416 } 417 } else { 418 doDumpDefault(in) 419 } 420 } 421 422 func doDumpString(in doDumpInternalInput) { 423 s := in.ReflectValue.String() 424 if !in.Option.WithType { 425 in.Buffer.WriteString(fmt.Sprintf(`"%v"`, addSlashesForString(s))) 426 } else { 427 in.Buffer.WriteString(fmt.Sprintf( 428 `%s(%d) "%v"`, 429 in.ReflectTypeName, 430 len(s), 431 addSlashesForString(s), 432 )) 433 } 434 } 435 436 func doDumpBool(in doDumpInternalInput) { 437 var s string 438 if in.ReflectValue.Bool() { 439 s = `true` 440 } else { 441 s = `false` 442 } 443 if in.Option.WithType { 444 s = fmt.Sprintf(`bool(%s)`, s) 445 } 446 in.Buffer.WriteString(s) 447 } 448 449 func doDumpDefault(in doDumpInternalInput) { 450 var s string 451 if in.ReflectValue.IsValid() && in.ReflectValue.CanInterface() { 452 s = fmt.Sprintf("%v", in.ReflectValue.Interface()) 453 } 454 if s == "" { 455 s = fmt.Sprintf("%v", in.Value) 456 } 457 s = gstr.Trim(s, `<>`) 458 if !in.Option.WithType { 459 in.Buffer.WriteString(s) 460 } else { 461 in.Buffer.WriteString(fmt.Sprintf("%s(%s)", in.ReflectTypeName, s)) 462 } 463 } 464 465 func addSlashesForString(s string) string { 466 return gstr.ReplaceByMap(s, map[string]string{ 467 `"`: `\"`, 468 "\r": `\r`, 469 "\t": `\t`, 470 "\n": `\n`, 471 }) 472 } 473 474 // DumpJson pretty dumps json content to stdout. 475 func DumpJson(value any) { 476 switch result := value.(type) { 477 case []byte: 478 doDumpJson(result) 479 case string: 480 doDumpJson([]byte(result)) 481 default: 482 jsonContent, err := json.Marshal(value) 483 if err != nil { 484 fmt.Println(err.Error()) 485 return 486 } 487 doDumpJson(jsonContent) 488 } 489 } 490 491 func doDumpJson(jsonContent []byte) { 492 var ( 493 buffer = bytes.NewBuffer(nil) 494 jsonBytes = jsonContent 495 ) 496 if err := json.Indent(buffer, jsonBytes, "", " "); err != nil { 497 fmt.Println(err.Error()) 498 } 499 fmt.Println(buffer.String()) 500 }