// Package conv provides utility functions related to type conversions,
// such as to int using ToAny[int](any), a slice or array to set type (map[bool]struct{}),
// map keys and values to slice in random order etc.
package convert
import "fmt"
// ToAny converts one type to another type.
func ToAny[T any](a any) T {
v, _ := ToAnyE[T](a)
return v
}
// ToAnyE converts one type to another and returns an error if error occurred.
func ToAnyE[T any](a any) (T, error) {
var t T
switch any(t).(type) {
case bool:
v, err := ToBoolE(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int:
v, err := ToIntE(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int8:
v, err := ToInt8E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int16:
v, err := ToInt16E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int32:
v, err := ToInt32E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int64:
v, err := ToInt64E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint:
v, err := ToUintE(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint8:
v, err := ToUint8E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint16:
v, err := ToUint16E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint32:
v, err := ToUint32E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint64:
v, err := ToUint64E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case float32:
v, err := ToFloat32E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case float64:
v, err := ToFloat64E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case string:
v, err := ToStringE(a)
if err != nil {
return t, err
}
t = any(v).(T)
default:
return t, fmt.Errorf("the type %T isn't supported", t)
}
return t, nil
}
package convert
import (
"encoding/json"
"errors"
"fmt"
"html/template"
"reflect"
"strconv"
"strings"
"time"
)
var errNegativeNotAllowed = errors.New("unable to cast negative value")
var (
errorType = reflect.TypeOf((*error)(nil)).Elem()
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
)
// ToBoolE casts any type to a bool type.
func ToBoolE(i any) (bool, error) {
i = indirect(i)
switch b := i.(type) {
case bool:
return b, nil
case nil:
return false, nil
case int:
if i.(int) != 0 {
return true, nil
}
return false, nil
case string:
return strconv.ParseBool(i.(string))
default:
return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
}
}
// ToIntE casts any type to an int type.
func ToIntE(i any) (int, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
return intv, nil
}
switch s := i.(type) {
case int64:
return int(s), nil
case int32:
return int(s), nil
case int16:
return int(s), nil
case int8:
return int(s), nil
case uint:
return int(s), nil
case uint64:
return int(s), nil
case uint32:
return int(s), nil
case uint16:
return int(s), nil
case uint8:
return int(s), nil
case float64:
return int(s), nil
case float32:
return int(s), nil
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
return int(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i)
case json.Number:
return ToIntE(string(s))
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to int", i, i)
}
}
// ToInt8E casts any type to an int8 type.
func ToInt8E(i any) (int8, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
return int8(intv), nil
}
switch s := i.(type) {
case int64:
return int8(s), nil
case int32:
return int8(s), nil
case int16:
return int8(s), nil
case int8:
return s, nil
case uint:
return int8(s), nil
case uint64:
return int8(s), nil
case uint32:
return int8(s), nil
case uint16:
return int8(s), nil
case uint8:
return int8(s), nil
case float64:
return int8(s), nil
case float32:
return int8(s), nil
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
return int8(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i)
case json.Number:
return ToInt8E(string(s))
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i)
}
}
// ToInt16E casts any type to an int16 type.
func ToInt16E(i any) (int16, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
return int16(intv), nil
}
switch s := i.(type) {
case int64:
return int16(s), nil
case int32:
return int16(s), nil
case int16:
return s, nil
case int8:
return int16(s), nil
case uint:
return int16(s), nil
case uint64:
return int16(s), nil
case uint32:
return int16(s), nil
case uint16:
return int16(s), nil
case uint8:
return int16(s), nil
case float64:
return int16(s), nil
case float32:
return int16(s), nil
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
return int16(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i)
case json.Number:
return ToInt16E(string(s))
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i)
}
}
// ToInt32E casts any type to an int32 type.
func ToInt32E(i any) (int32, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
return int32(intv), nil
}
switch s := i.(type) {
case int64:
return int32(s), nil
case int32:
return s, nil
case int16:
return int32(s), nil
case int8:
return int32(s), nil
case uint:
return int32(s), nil
case uint64:
return int32(s), nil
case uint32:
return int32(s), nil
case uint16:
return int32(s), nil
case uint8:
return int32(s), nil
case float64:
return int32(s), nil
case float32:
return int32(s), nil
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
return int32(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i)
case json.Number:
return ToInt32E(string(s))
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i)
}
}
// ToInt64E casts any to an int64 type.
func ToInt64E(i any) (int64, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
return int64(intv), nil
}
switch s := i.(type) {
case int64:
return s, nil
case int32:
return int64(s), nil
case int16:
return int64(s), nil
case int8:
return int64(s), nil
case uint:
return int64(s), nil
case uint64:
return int64(s), nil
case uint32:
return int64(s), nil
case uint16:
return int64(s), nil
case uint8:
return int64(s), nil
case float64:
return int64(s), nil
case float32:
return int64(s), nil
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
return v, nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i)
case json.Number:
return ToInt64E(string(s))
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i)
}
}
// ToUintE casts any type to a uint type.
func ToUintE(i any) (uint, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint(intv), nil
}
switch s := i.(type) {
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to uint", i, i)
case json.Number:
return ToUintE(string(s))
case int64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint(s), nil
case int32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint(s), nil
case int16:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint(s), nil
case int8:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint(s), nil
case uint:
return s, nil
case uint64:
return uint(s), nil
case uint32:
return uint(s), nil
case uint16:
return uint(s), nil
case uint8:
return uint(s), nil
case float64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint(s), nil
case float32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint(s), nil
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to uint", i, i)
}
}
// ToUint8E casts any type to a uint type.
func ToUint8E(i any) (uint8, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint8(intv), nil
}
switch s := i.(type) {
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint8(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to uint8", i, i)
case json.Number:
return ToUint8E(string(s))
case int64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint8(s), nil
case int32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint8(s), nil
case int16:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint8(s), nil
case int8:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint8(s), nil
case uint:
return uint8(s), nil
case uint64:
return uint8(s), nil
case uint32:
return uint8(s), nil
case uint16:
return uint8(s), nil
case uint8:
return s, nil
case float64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint8(s), nil
case float32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint8(s), nil
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to uint8", i, i)
}
}
// ToUint16E casts any type to a uint16 type.
func ToUint16E(i any) (uint16, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint16(intv), nil
}
switch s := i.(type) {
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint16(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to uint16", i, i)
case json.Number:
return ToUint16E(string(s))
case int64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint16(s), nil
case int32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint16(s), nil
case int16:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint16(s), nil
case int8:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint16(s), nil
case uint:
return uint16(s), nil
case uint64:
return uint16(s), nil
case uint32:
return uint16(s), nil
case uint16:
return s, nil
case uint8:
return uint16(s), nil
case float64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint16(s), nil
case float32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint16(s), nil
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to uint16", i, i)
}
}
// ToUint32E casts any type to a uint32 type.
func ToUint32E(i any) (uint32, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint32(intv), nil
}
switch s := i.(type) {
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint32(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to uint32", i, i)
case json.Number:
return ToUint32E(string(s))
case int64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint32(s), nil
case int32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint32(s), nil
case int16:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint32(s), nil
case int8:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint32(s), nil
case uint:
return uint32(s), nil
case uint64:
return uint32(s), nil
case uint32:
return s, nil
case uint16:
return uint32(s), nil
case uint8:
return uint32(s), nil
case float64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint32(s), nil
case float32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint32(s), nil
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to uint32", i, i)
}
}
// ToUint64E casts any type to a uint64 type.
func ToUint64E(i any) (uint64, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint64(intv), nil
}
switch s := i.(type) {
case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint64(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to uint64", i, i)
case json.Number:
return ToUint64E(string(s))
case int64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint64(s), nil
case int32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint64(s), nil
case int16:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint64(s), nil
case int8:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint64(s), nil
case uint:
return uint64(s), nil
case uint64:
return s, nil
case uint32:
return uint64(s), nil
case uint16:
return uint64(s), nil
case uint8:
return uint64(s), nil
case float32:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint64(s), nil
case float64:
if s < 0 {
return 0, errNegativeNotAllowed
}
return uint64(s), nil
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to uint64", i, i)
}
}
// ToFloat32E casts any type to a float32 type.
func ToFloat32E(i any) (float32, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
return float32(intv), nil
}
switch s := i.(type) {
case float64:
return float32(s), nil
case float32:
return s, nil
case int64:
return float32(s), nil
case int32:
return float32(s), nil
case int16:
return float32(s), nil
case int8:
return float32(s), nil
case uint:
return float32(s), nil
case uint64:
return float32(s), nil
case uint32:
return float32(s), nil
case uint16:
return float32(s), nil
case uint8:
return float32(s), nil
case string:
v, err := strconv.ParseFloat(s, 32)
if err == nil {
return float32(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
case json.Number:
v, err := s.Float64()
if err == nil {
return float32(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
}
}
// ToFloat64E casts any type to a float64 type.
func ToFloat64E(i any) (float64, error) {
i = indirect(i)
intv, ok := toInt(i)
if ok {
return float64(intv), nil
}
switch s := i.(type) {
case float64:
return s, nil
case float32:
return float64(s), nil
case int64:
return float64(s), nil
case int32:
return float64(s), nil
case int16:
return float64(s), nil
case int8:
return float64(s), nil
case uint:
return float64(s), nil
case uint64:
return float64(s), nil
case uint32:
return float64(s), nil
case uint16:
return float64(s), nil
case uint8:
return float64(s), nil
case string:
v, err := strconv.ParseFloat(s, 64)
if err == nil {
return v, nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
case json.Number:
v, err := s.Float64()
if err == nil {
return v, nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
case bool:
if s {
return 1, nil
}
return 0, nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
}
}
// ToStringE casts any type to a string type.
func ToStringE(i any) (string, error) {
i = indirectToStringerOrError(i)
switch s := i.(type) {
case string:
return s, nil
case bool:
return strconv.FormatBool(s), nil
case float64:
return strconv.FormatFloat(s, 'f', -1, 64), nil
case float32:
return strconv.FormatFloat(float64(s), 'f', -1, 32), nil
case int:
return strconv.Itoa(s), nil
case int64:
return strconv.FormatInt(s, 10), nil
case int32:
return strconv.Itoa(int(s)), nil
case int16:
return strconv.FormatInt(int64(s), 10), nil
case int8:
return strconv.FormatInt(int64(s), 10), nil
case uint:
return strconv.FormatUint(uint64(s), 10), nil
case uint64:
return strconv.FormatUint(uint64(s), 10), nil
case uint32:
return strconv.FormatUint(uint64(s), 10), nil
case uint16:
return strconv.FormatUint(uint64(s), 10), nil
case uint8:
return strconv.FormatUint(uint64(s), 10), nil
case json.Number:
return s.String(), nil
case []byte:
return string(s), nil
case template.HTML:
return string(s), nil
case template.URL:
return string(s), nil
case template.JS:
return string(s), nil
case template.CSS:
return string(s), nil
case template.HTMLAttr:
return string(s), nil
case nil:
return "", nil
case fmt.Stringer:
return s.String(), nil
case error:
return s.Error(), nil
default:
return "", fmt.Errorf("unable to cast %#v of type %T to string", i, i)
}
}
// ToDurationE casts any type to time.Duration type.
func ToDurationE(i any) (d time.Duration, err error) {
i = indirect(i)
switch s := i.(type) {
case time.Duration:
return s, nil
case int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8:
d = time.Duration(ToAny[int64](s))
return
case float32, float64:
d = time.Duration(ToAny[float64](s))
return
case string:
if strings.ContainsAny(s, "nsuµmh") {
d, err = time.ParseDuration(s)
} else {
d, err = time.ParseDuration(s + "ns")
}
return
case json.Number:
var v float64
v, err = s.Float64()
d = time.Duration(v)
return
default:
err = fmt.Errorf("unable to cast %#v of type %T to Duration", i, i)
return
}
}
// toInt returns the int value of v if v or v's underlying type
// is an int.
// Note that this will return false for int64 etc. types.
func toInt(v any) (int, bool) {
switch v := v.(type) {
case int:
return v, true
case time.Weekday:
return int(v), true
case time.Month:
return int(v), true
default:
return 0, false
}
}
// Copied from html/template/content.go.
// indirect returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil).
func indirect(a any) any {
if a == nil {
return nil
}
if t := reflect.TypeOf(a); t.Kind() != reflect.Pointer {
// Avoid creating a reflect.Value if it's not a pointer.
return a
}
v := reflect.ValueOf(a)
for v.Kind() == reflect.Pointer && !v.IsNil() {
v = v.Elem()
}
return v.Interface()
}
// Copied from html/template/content.go.
// indirectToStringerOrError returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
// or error,
func indirectToStringerOrError(a any) any {
if a == nil {
return nil
}
v := reflect.ValueOf(a)
for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Pointer && !v.IsNil() {
v = v.Elem()
}
return v.Interface()
}
// trimZeroDecimal trims the zero decimal.
// E.g. 12.00 to 12 while 12.01 still to be 12.01.
func trimZeroDecimal(s string) string {
var foundZero bool
for i := len(s); i > 0; i-- {
switch s[i-1] {
case '.':
if foundZero {
return s[:i-1]
}
case '0':
foundZero = true
default:
return s
}
}
return s
}