A tiny go package that helps converting values from/to a string.
var yesno bool
sb, err := stringable.New(&yesno)
sb.FromString("true")
sb.ToString()
- string, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128
time.Time
[]byte
When calling stringable.New(x)
with an instance x
that is not a Stringable itself, nor any of the above builtin types, it will try to create a "hybrid" Stringable instance from x
for you.
Here is how the "hybrid" Stringable instance will be created:
- Create a hybrid instance
h
from the given instancex
; - If
x
has implemented one ofstringable.StringMarshaler
andencoding.TextMarshaler
,h
will use it as the implementation ofstringable.StringMarshaler
, i.e. theToString()
method; - If
x
has implemented one ofstringable.StringUnmarshaler
andencoding.TextUnmarshaler
,h
will use it as the implementation ofstringable.StringUnmarshaler
, i.e. theFromString()
method; - As long as
h
has an implementation of eitherstringable.StringMarshaler
orstringable.StringUnmarshaler
, we considerh
is a validStringable
instance. You can require both by passing in aCompleteHybrid()
option toNew
method. For a validh
,stringable.New(x)
will returnh
. Otherwise, anErrUnsupportedType
occurs.
Example:
type Location struct {
X int
Y int
}
func (l *Location) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("L(%d,%d)", l.X, l.Y)), nil
}
loc := &Location{3, 4}
sb, err := stringable.New(loc) // err is nil
sb.ToString() // L(3,4)
sb.FromString("L(5,6)") // ErrNotStringUnmarshaler, "not a StringUnmarshaler"
New(v, NoHybrid())
: preventNew
from trying to create a hybrid instance fromv
at all. Instead, returnsErrUnsupportedType
.New(v, CompleteHybrid())
: still allowNew
trying to create a hybrid instance fromv
if necessary, but with the present ofCompleteHybrid()
option, the returned hybrid instance must have a valid implementation of bothFromString
andToString
.
The Namespace.Adapt()
API is used to customize the behaviour of stringable.Stringable
of a specific type. The principal is to create a type alias to the target type you want to override, and implement the Stringable
interface on the new type.
When should you use this API?
- change the conversion logic of the builtin types.
- change the conversion logic of existing types that are "hybridizable", but you don't want to change their implementations.
For example, the default support of bool
type in this package uses strconv.ParseBool
method to convert strings like "true", "TRUE", "f", "0", etc. to a bool value. If you want to support also converting "YES", "NO", "はい" to a bool value, you can implement a custom bool type and register it to a Namespace
instance:
type YesNo bool
func (yn YesNo) ToString() (string, error) {
if yn {
return "yes", nil
} else {
return "no", nil
}
}
func (yn *YesNo) FromString(s string) error {
switch strings.ToLower(s) {
case "yes":
*yn = true
case "no":
*yn = false
default:
return errors.New("invalid value")
}
return nil
}
func main() {
ns := stringable.NewNamespace()
typ, adaptor := ToAnyStringableAdaptor(func(b *bool) (Stringable, error) {
return (*YesNo)(b), nil
})
ns.Adapt(typ, adaptor)
var yesno bool = true
sb, err := ns.New(&yesno)
}