package typekit //package main import ( "fmt" "reflect" "sort" "strconv" "strings" "unsafe" ) const ( colorReset = "\033[0m" colorStruct = "\033[34m\033[7m" colorField = "\033[32m" colorMap = "\033[33m\033[7m" colorSlice = "\033[35m\033[7m" colorValue = "\033[31m" colorKey = "\033[36m" symbolVert = "│ " symbolLast = "└─ " symbolMid = "├─ " symbolEmpty = " " ) type PrintOptions struct { ShowTypes bool } func PrettyPrint(v interface{}, opts ...PrintOptions) string { option := PrintOptions{ShowTypes: true} if len(opts) > 0 { option = opts[0] } return prettyPrint(reflect.ValueOf(v), 0, make(map[uintptr]bool), option, []bool{}).String() } type lines []string func (l lines) String() string { return strings.Join(l, "\n") } func buildPrefix(indent int, isLasts []bool) string { var prefix strings.Builder for i := 0; i < indent; i++ { if i < len(isLasts) { if isLasts[i] { prefix.WriteString(symbolEmpty) } else { prefix.WriteString(symbolVert) } } } return prefix.String() } func prettyPrint(v reflect.Value, indentLevel int, visited map[uintptr]bool, opts PrintOptions, isLasts []bool) lines { original := v for { switch v.Kind() { case reflect.Ptr: if v.IsNil() { return lines{colorValue + "nil" + colorReset} } ptr := v.Pointer() if visited[ptr] { return lines{fmt.Sprintf("%s&%p%s", colorValue, v.Interface(), colorReset)} } visited[ptr] = true original = v v = v.Elem() case reflect.Interface: if v.IsNil() { return lines{colorValue + "nil" + colorReset} } v = v.Elem() default: goto END_DEREF } } END_DEREF: switch v.Kind() { case reflect.Struct: return printStruct(v, indentLevel, visited, opts, isLasts) case reflect.Map: return printMap(v, indentLevel, visited, opts, isLasts) case reflect.Slice, reflect.Array: return printSlice(v, indentLevel, visited, opts, isLasts) case reflect.String: return lines{fmt.Sprintf("%s%q%s", colorValue, v.String(), colorReset)} case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return lines{fmt.Sprintf("%s%d%s", colorValue, v.Int(), colorReset)} case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return lines{fmt.Sprintf("%s%d%s", colorValue, v.Uint(), colorReset)} case reflect.Float32, reflect.Float64: // 使用精确的字符串表示,保留必要精度 str := strconv.FormatFloat(v.Float(), 'f', -1, 64) // 移除多余的末尾零和小数点 if strings.Contains(str, ".") { str = strings.TrimRight(str, "0") str = strings.TrimRight(str, ".") } return lines{fmt.Sprintf("%s%s%s", colorValue, str, colorReset)} case reflect.Bool: return lines{fmt.Sprintf("%s%t%s", colorValue, v.Bool(), colorReset)} default: if original.Kind() == reflect.Ptr { return lines{fmt.Sprintf("%s%v%s", colorValue, original.Interface(), colorReset)} } //return lines{fmt.Sprintf("%s%v%s", colorValue, v.Interface(), colorReset)} return lines{formatUnexported(v, original)} } } // 新增辅助函数处理未导出字段 func formatUnexported(v, original reflect.Value) string { var value interface{} var canShow bool // 尝试通过不同方式安全获取值 switch { case v.CanInterface(): value = v.Interface() canShow = true case original.Kind() == reflect.Ptr && original.CanInterface(): value = original.Interface() canShow = true default: canShow = false } // 特殊处理指针类型 if v.Kind() == reflect.Ptr && !v.IsNil() { return fmt.Sprintf("%s&%p%s", colorValue, unsafe.Pointer(v.Pointer()), colorReset) } // 构造显示字符串 if canShow { return fmt.Sprintf("%s%v%s", colorValue, value, colorReset) } return fmt.Sprintf("%s%s", colorValue, colorReset) } func printStruct(v reflect.Value, indentLevel int, visited map[uintptr]bool, opts PrintOptions, parentLasts []bool) lines { var header string if opts.ShowTypes { header = fmt.Sprintf("%s%s%s", colorStruct, v.Type().String(), colorReset) } else { header = " ---" } result := lines{header} fieldCount := v.NumField() for i := 0; i < fieldCount; i++ { field := v.Type().Field(i) // 跳过未导出字段 if !field.IsExported() { continue } fieldValue := v.Field(i) isLast := i == fieldCount-1 prefix := buildPrefix(indentLevel, parentLasts) currentLasts := append(parentLasts, isLast) var symbol string if isLast { symbol = symbolLast } else { symbol = symbolMid } fieldLines := prettyPrint(fieldValue, indentLevel+1, visited, opts, currentLasts) fieldHeader := fmt.Sprintf("%s%s%s%s: ", prefix, symbol, colorField, field.Name, ) if len(fieldLines) == 0 { result = append(result, fieldHeader+colorValue+""+colorReset) continue } result = append(result, fieldHeader+fieldLines[0]) for _, line := range fieldLines[1:] { result = append(result, prefix+symbolEmpty+line) } } return result } func printMap(v reflect.Value, indentLevel int, visited map[uintptr]bool, opts PrintOptions, parentLasts []bool) lines { var header string if opts.ShowTypes { header = fmt.Sprintf("%s%s%s", colorMap, v.Type().String(), colorReset) } else { header = " ---" } result := lines{header} keys := v.MapKeys() sort.Slice(keys, func(i, j int) bool { return fmt.Sprintf("%v", keys[i]) < fmt.Sprintf("%v", keys[j]) }) keyCount := len(keys) for i, key := range keys { prefix := buildPrefix(indentLevel, parentLasts) isLast := i == keyCount-1 currentLasts := append(parentLasts, isLast) var symbol string if isLast { symbol = symbolLast } else { symbol = symbolMid } keyLines := prettyPrint(key, 0, visited, opts, currentLasts) valueLines := prettyPrint(v.MapIndex(key), indentLevel+1, visited, opts, currentLasts) keyHeader := fmt.Sprintf("%s%s%s%s: ", prefix, symbol, colorKey, strings.Join(keyLines, ""), ) if len(valueLines) == 0 { result = append(result, keyHeader+colorValue+""+colorReset) continue } result = append(result, keyHeader+valueLines[0]) for _, line := range valueLines[1:] { result = append(result, prefix+symbolEmpty+line) } } return result } func printSlice(v reflect.Value, indentLevel int, visited map[uintptr]bool, opts PrintOptions, parentLasts []bool) lines { var header string if opts.ShowTypes { header = fmt.Sprintf("%s%s%s", colorSlice, v.Type().String(), colorReset) } else { header = " ---" } result := lines{header} elemCount := v.Len() for i := 0; i < elemCount; i++ { prefix := buildPrefix(indentLevel, parentLasts) isLast := i == elemCount-1 currentLasts := append(parentLasts, isLast) var symbol string if isLast { symbol = symbolLast } else { symbol = symbolMid } elemLines := prettyPrint(v.Index(i), indentLevel+1, visited, opts, currentLasts) bullet := fmt.Sprintf("%s%s", prefix, symbol) if len(elemLines) == 0 { result = append(result, bullet+colorValue+""+colorReset) continue } result = append(result, bullet+elemLines[0]) for _, line := range elemLines[1:] { result = append(result, prefix+symbolEmpty+line) } } return result } /* type Person struct { Name string Age int Hobbies []string Address struct { City string State string } Metadata map[string]interface{} } func main() { p := Person{ Name: "Alice", Age: 30, Hobbies: []string{"Reading", "Hiking"}, Metadata: map[string]interface{}{ "id": 123, "scores": []float64{98.5, 87.2}, "nested": map[string]interface{}{"a": 1, "b": 2}, }, } p.Address.City = "New York" p.Address.State = "NY" p.Metadata["self_ref"] = &p fmt.Println("With types:") fmt.Println(PrettyPrint(p, PrintOptions{ShowTypes: false})) fmt.Println("\nWithout types:\n") fmt.Println(PrettyPrint(p)) } */