'How to reduce code duplication in function which converts basic types to string in Go

I've written a simple function in Go (1.18.1), which takes any (a.k. interface{}) type as input and returns the value as string, in case of a nil pointer, the function returns "nil"; in case the type is not supported, the function returns "?"

// small helper funcion which is usefull when printing (pointer) values
func ToString(T any) string {
    switch v := T.(type) {
    case string:
        return v
    case *string:
        if v == nil {
            return "nil"
        } else {
            return *v
        }

    case int:
        return strconv.FormatInt(int64(v), 10)
    case int8:
        return strconv.FormatInt(int64(v), 10)
    case int16:
        return strconv.FormatInt(int64(v), 10)
    case int32:
        return strconv.FormatInt(int64(v), 10)
    case int64:
        return strconv.FormatInt(v, 10)
    case float32:
        return fmt.Sprintf("%f", v)
    case float64:
        return fmt.Sprintf("%f", v)

    case *int:
        if v == nil {
            return "nil"
        }
        return strconv.FormatInt(int64(*v), 10)
    case *int8:
        if v == nil {
            return "nil"
        }
        return strconv.FormatInt(int64(*v), 10)
    case *int16:
        if v == nil {
            return "nil"
        }
        return strconv.FormatInt(int64(*v), 10)
    case *int32:
        if v == nil {
            return "nil"
        }
        return strconv.FormatInt(int64(*v), 10)
    case *int64:
        if v == nil {
            return "nil"
        }
        return strconv.FormatInt(*v, 10)
    case *float32:
        if v == nil {
            return "nil"
        }
        return fmt.Sprintf("%f", *v)
    case *float64:
        if v == nil {
            return "nil"
        }
        return fmt.Sprintf("%f", *v)

    case *bool:
        if v == nil {
            return "nil"
        }
    case bool:
        if v == true {
            return "true"
        }
        return "false"
    }
    return "?"
}

This does its job perfectly, but looking at the actual algorithm I'm irritated by the amount of code duplication, unfortunately a fallthrough doesn't work in a type switch statement (see Why isn't fallthrough allowed in a type switch?).

Are there any more efficient ways (i.e. with less code duplication), to do the same thing as the above ToString(T any) function?

go


Solution 1:[1]

Shorter code with reflect:

func ToString(x any) string {
    v := reflect.ValueOf(x)
    if v.Kind() == reflect.Ptr {
        if v.IsZero() {
            return "nil"
        }
        v = v.Elem()
    }

    switch v.Kind() {
    case reflect.String:
        return v.String()
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return strconv.FormatInt(v.Int(), 10)
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        return strconv.FormatUint(v.Uint(), 10)
    case reflect.Float32, reflect.Float64:
        return strconv.FormatFloat(v.Float(), 'f', -1, v.Type().Bits())
    case reflect.Bool:
        return strconv.FormatBool(v.Bool())
    default:
        return "?"
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1