Skip to content

Commit

Permalink
Fix slice TextUnmarshaler. (#99)
Browse files Browse the repository at this point in the history
Fixes handling of cases when a custome type is both a TextUnmarshaler
and a slice.
  • Loading branch information
emil2k authored and kisielk committed Dec 28, 2017
1 parent 4663607 commit afe7739
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 4 deletions.
10 changes: 6 additions & 4 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ func isEmpty(t reflect.Type, value []string) bool {

// decode fills a struct field using a parsed path.
func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values []string) error {

// Get the field walking the struct fields by index.
for _, name := range parts[0].path {
if v.Type().Kind() == reflect.Ptr {
Expand Down Expand Up @@ -185,7 +184,8 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values

// Get the converter early in case there is one for a slice type.
conv := d.cache.converter(t)
if conv == nil && t.Kind() == reflect.Slice {
m := isTextUnmarshaler(v)
if conv == nil && t.Kind() == reflect.Slice && m.IsSlice {
var items []reflect.Value
elemT := t.Elem()
isPtrElem := elemT.Kind() == reflect.Ptr
Expand All @@ -209,7 +209,7 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
if d.zeroEmpty {
items = append(items, reflect.Zero(elemT))
}
} else if m := isTextUnmarshaler(v); m.IsValid {
} else if m.IsValid {
u := reflect.New(elemT)
if m.IsPtr {
u = reflect.New(reflect.PtrTo(elemT).Elem())
Expand Down Expand Up @@ -297,7 +297,7 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
Index: -1,
}
}
} else if m := isTextUnmarshaler(v); m.IsValid {
} else if m.IsValid {
// If the value implements the encoding.TextUnmarshaler interface
// apply UnmarshalText as the converter
if err := m.Unmarshaler.UnmarshalText([]byte(val)); err != nil {
Expand Down Expand Up @@ -346,6 +346,7 @@ func isTextUnmarshaler(v reflect.Value) unmarshaler {
}
if t.Kind() == reflect.Slice {
// if t is a pointer slice, check if it implements encoding.TextUnmarshaler
m.IsSlice = true
if t = t.Elem(); t.Kind() == reflect.Ptr {
t = reflect.PtrTo(t.Elem())
v = reflect.Zero(t)
Expand All @@ -364,6 +365,7 @@ func isTextUnmarshaler(v reflect.Value) unmarshaler {
// unmarshaller contains information about a TextUnmarshaler type
type unmarshaler struct {
Unmarshaler encoding.TextUnmarshaler
IsSlice bool
IsPtr bool
IsValid bool
}
Expand Down
34 changes: 34 additions & 0 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1657,3 +1657,37 @@ func TestRegisterConverterOverridesTextUnmarshaler(t *testing.T) {
t.Errorf("s1.Aa: expected %v, got %v", ts, s1.MyTime)
}
}

type S20E string

func (e *S20E) UnmarshalText(text []byte) error {
*e = S20E("x")
return nil
}

type S20 []S20E

func (s *S20) UnmarshalText(text []byte) error {
*s = S20{"a", "b", "c"}
return nil
}

// Test to ensure that when a custom type based on a slice implements an
// encoding.TextUnmarshaler interface that it takes precedence over any
// implementations by its elements.
func TestTextUnmarshalerTypeSlice(t *testing.T) {
data := map[string][]string{
"Value": []string{"a,b,c"},
}
s := struct {
Value S20
}{}
decoder := NewDecoder()
if err := decoder.Decode(&s, data); err != nil {
t.Error("Error while decoding:", err)
}
expected := S20{"a", "b", "c"}
if !reflect.DeepEqual(expected, s.Value) {
t.Errorf("Expected %v errors, got %v", expected, s.Value)
}
}

0 comments on commit afe7739

Please sign in to comment.