From 3a035a19bd644ddad5431a7ee220eda7ccb813f0 Mon Sep 17 00:00:00 2001 From: Alex Flint Date: Mon, 19 Apr 2021 22:23:18 -0700 Subject: [PATCH] deal with chan, map, and slice types that implement TextUnmarshaler --- scalar.go | 47 +++++++++++++++++++++++++++++++++++++++++++---- scalar_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/scalar.go b/scalar.go index 073392c..14df3c7 100644 --- a/scalar.go +++ b/scalar.go @@ -31,6 +31,42 @@ func Parse(dest interface{}, s string) error { return ParseValue(reflect.ValueOf(dest), s) } +func parseAsTextUnmarshaler(v reflect.Value, s string) (bool, error) { + t := v.Type() + if !t.Implements(textUnmarshalerType) { + return false, nil + } + + fmt.Printf("parsing into %v: IsNil=%v, CanSet=%v, Kind=%v\n", t, v.IsNil(), v.CanSet(), t.Kind()) + + if v.IsNil() && v.CanSet() { + switch t.Kind() { + case reflect.Ptr: + v.Set(reflect.New(v.Type().Elem())) + case reflect.Slice: + v.Set(reflect.MakeSlice(t, 0, 0)) + case reflect.Map: + v.Set(reflect.MakeMap(t)) + case reflect.Chan: + v.Set(reflect.MakeChan(t, 0)) + } + } + + if !v.IsNil() && t.Kind() == reflect.Ptr { + switch t.Elem().Kind() { + case reflect.Slice: + v.Elem().Set(reflect.MakeSlice(t.Elem(), 0, 0)) + case reflect.Map: + v.Elem().Set(reflect.MakeMap(t.Elem())) + case reflect.Chan: + v.Elem().Set(reflect.MakeChan(t.Elem(), 0)) + } + } + + err := v.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(s)) + return true, err +} + // ParseValue assigns a value to v by parsing s. func ParseValue(v reflect.Value, s string) error { // If we have a nil pointer then allocate a new object @@ -43,14 +79,17 @@ func ParseValue(v reflect.Value, s string) error { } // If it implements encoding.TextUnmarshaler then use that - if scalar, ok := v.Interface().(encoding.TextUnmarshaler); ok { - return scalar.UnmarshalText([]byte(s)) + fmt.Println("attempt 1...") + if matched, err := parseAsTextUnmarshaler(v, s); matched { + return err } + // If it's a value instead of a pointer, check that we can unmarshal it // via TextUnmarshaler as well if v.CanAddr() { - if scalar, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok { - return scalar.UnmarshalText([]byte(s)) + fmt.Println("attempt 2...") + if matched, err := parseAsTextUnmarshaler(v, s); matched { + return err } } diff --git a/scalar_test.go b/scalar_test.go index 9a1ef6a..27cd592 100644 --- a/scalar_test.go +++ b/scalar_test.go @@ -87,3 +87,39 @@ func TestParse(t *testing.T) { require.NoError(t, err) assert.Equal(t, 123, v) } + +type sliceUnmarshaler []int + +func (sliceUnmarshaler) UnmarshalText(b []byte) error { + return nil +} + +type mapUnmarshaler map[string]string + +func (m mapUnmarshaler) UnmarshalText(b []byte) error { + m["a"] = string(b) + return nil +} + +type chanUnmarshaler chan string + +func (ch chanUnmarshaler) UnmarshalText(b []byte) error { + return nil +} + +func TestParseReferenceTypes(t *testing.T) { + var err error + + var s sliceUnmarshaler + err = Parse(&s, "test1") + require.NoError(t, err) + + var m mapUnmarshaler + err = Parse(&m, "test2") + require.NoError(t, err) + assert.Equal(t, "test2", m["a"]) + + var c chanUnmarshaler + err = Parse(&c, "test3") + require.NoError(t, err) +}