DEV Community

HM
HM

Posted on

go.reflect for a custom Print function

#go

Below, we create a function called Display that works like Println but even better: it shows the path traversed along with the value.

e.g.
Display([]string{"hello", "world"})
would print
root.0 = hello
root.1 = world

package main

import (
    "fmt"
    "reflect"
    "strconv"
)

func main() {
    Display("hi")
    Display([]string{"apple", "banana"})
    Display([...]string{"apple", "banana"})
    var i interface{} = 3
    Display(i)
    type astrct struct {
        Z string
    }
    type strct struct {
        A string
        B int
        astrct
    }
    Display(strct{"js", 1234, astrct{"nodejs"}})
    Display(&strct{"js", 1234, astrct{"nodejs"}})
    Display(map[string]int{"m1": 1, "m2": 2})
}

func Display(d interface{}) {
    fmt.Printf("-- Display for %T --\n", d)
    display("root", reflect.ValueOf(d))
}

func display(path string, v reflect.Value) {
    switch v.Kind() {
    case reflect.Slice, reflect.Array:
        for i := 0; i < v.Len(); i++ {
            display(fmt.Sprintf("%s.%v", path, i), v.Index(i))
        }
    case reflect.Struct:
        for i := 0; i < v.NumField(); i++ {
            display(fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name), v.Field(i))
        }
    case reflect.Ptr:
        if v.IsNil() {
            fmt.Println("%s = nil", path)
        } else {
            display(fmt.Sprintf("*%s", path), v.Elem())
        }
    case reflect.Map:
        if v.IsNil() {
            fmt.Println("%s = nil", path)
        } else {
            for _, i := range v.MapKeys() {
                display(fmt.Sprintf("%s[%s]", path, i), v.MapIndex(i))
            }

        }
    case reflect.Interface:
        display(fmt.Sprintf("%s.value", path), v.Elem())

    default:
        fmt.Printf("%s = %s\n", path, formatToPrint(v))
    }
}

// formatToPrint formats a value without inspecting its internal structure.
func formatToPrint(v reflect.Value) string {
    switch k := v.Kind(); {
    case k == 0:
        return "invalid kind"
    case k == 1:
        return strconv.FormatBool(v.Bool())
    case k >= 2 && k <= 6:
        return strconv.FormatInt(v.Int(), 10)
    case k >= 7 && k <= 12:
        return strconv.FormatUint(v.Uint(), 10)
    case k >= 13 && k <= 14:
        return strconv.FormatFloat(v.Float(), 'E', -1, 64)
    case k >= 15 && k <= 16:
        return strconv.FormatComplex(v.Complex(), 'E', -1, 64)
    case k == 18 || k == 19 || k == 21 || k == 22 || k == 23:
        // reference types:
        // chan, func, map, ptr, Slice
        return "type=" + v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
    case k == 24:
        // string
        return strconv.Quote(v.String())
    case k == 17 || k == 20 || k == 25:
        // aggregate types:
        // array, interface, struct
        return "type=" + v.Type().String()
    case k == 26:
        // unsafeptr
        return "unsafe ptr"
    default:
        return "N/A"
    }
}

Enter fullscreen mode Exit fullscreen mode

Output from above code =

-- Display for string --
root = "hi"
-- Display for []string --
root.0 = "apple"
root.1 = "banana"
-- Display for [2]string --
root.0 = "apple"
root.1 = "banana"
-- Display for int --
root = 3
-- Display for main.strct --
root.A = "js"
root.B = 1234
root.astrct.Z = "nodejs"
-- Display for *main.strct --
*root.A = "js"
*root.B = 1234
*root.astrct.Z = "nodejs"
-- Display for map[string]int --
root[m1] = 1
root[m2] = 2
Enter fullscreen mode Exit fullscreen mode

Top comments (0)