Skip to content

Commit

Permalink
Merge pull request #86 from frictionlessdata/castEmbed
Browse files Browse the repository at this point in the history
Adding support cast rows to structs with embedded types
  • Loading branch information
danielfireman committed Aug 28, 2020
2 parents 0292a8c + 32640ff commit 1c5741f
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 21 deletions.
76 changes: 55 additions & 21 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,38 +196,72 @@ func (s *Schema) CastRow(row []string, out interface{}) error {
return fmt.Errorf("can only cast pointer to structs")
}
if len(row) != len(s.Fields) {
return fmt.Errorf("The row with %d values does not match the %d fields in the schema", len(row), len(s.Fields))
return fmt.Errorf("the row with %d values does not match the %d fields in the schema", len(row), len(s.Fields))
}
fields, err := getStructFields(out)
if err != nil {
return fmt.Errorf("error extracting field information from the struct:%q", err)
}
for _, f := range fields {
fieldName, ok := f.StructField.Tag.Lookup(tableheaderTag)
if !ok { // if no tag is set use own name
fieldName = f.StructField.Name
}
schemaField, fieldIndex := s.GetField(fieldName)
if fieldIndex != InvalidPosition {
cell := row[fieldIndex]
if s.isMissingValue(cell) {
continue
}
v, err := schemaField.Cast(cell)
if err != nil {
return err
}
if err := f.Set(v); err != nil {
return err
}
}
}
return nil
}

type structField struct {
reflect.StructField
value reflect.Value
}

func (s *structField) Set(v interface{}) error {
toSetValue := reflect.ValueOf(v)
toSetType := toSetValue.Type()
if !toSetType.ConvertibleTo(s.Type) {
return fmt.Errorf("field:%s value:%v - cannot convert from %v to %v", s.Name, v, toSetType, s.Type)
}
s.value.Set(toSetValue.Convert(s.Type))
return nil
}

func getStructFields(out interface{}) ([]structField, error) {
if reflect.ValueOf(out).Kind() != reflect.Ptr || reflect.Indirect(reflect.ValueOf(out)).Kind() != reflect.Struct {
return nil, fmt.Errorf("can only cast pointer to structs")
}
var fields []structField
outv := reflect.Indirect(reflect.ValueOf(out))
outt := outv.Type()
for i := 0; i < outt.NumField(); i++ {
fieldValue := outv.Field(i)
if fieldValue.CanSet() { // Only consider exported fields.
field := outt.Field(i)
fieldName, ok := field.Tag.Lookup(tableheaderTag)
if !ok { // if no tag is set use own name
fieldName = field.Name
}
f, fieldIndex := s.GetField(fieldName)
if fieldIndex != InvalidPosition {
cell := row[fieldIndex]
if s.isMissingValue(cell) {
continue
}
v, err := f.Cast(cell)
if fieldValue.Kind() == reflect.Struct {
newF, err := getStructFields(fieldValue.Addr().Interface())
if err != nil {
return err
}
toSetValue := reflect.ValueOf(v)
toSetType := toSetValue.Type()
if !toSetType.ConvertibleTo(field.Type) {
return fmt.Errorf("value:%s field:%s - can not convert from %v to %v", field.Name, cell, toSetType, field.Type)
return nil, err
}
fieldValue.Set(toSetValue.Convert(field.Type))
fields = append(fields, newF...)
continue
}
fields = append(fields, structField{outt.Field(i), fieldValue})
}
}
return nil
return fields, nil
}

type rawCell struct {
Expand Down
14 changes: 14 additions & 0 deletions schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,20 @@ func TestSchema_Cast(t *testing.T) {
is.Equal(t1.MyName, "Foo")
is.Equal(t1.MyAge, int64(42))
})
t.Run("EmbeddedStruct", func(t *testing.T) {
is := is.New(t)
type EmbededT struct {
MyAge int64 `tableheader:"Age"`
}
t1 := struct {
MyName string `tableheader:"Name"`
EmbededT
}{}
s := Schema{Fields: []Field{{Name: "Name", Type: StringType}, {Name: "Age", Type: IntegerType}}}
is.NoErr(s.CastRow([]string{"Foo", "42"}, &t1)) // BOOOOOO
is.Equal(t1.MyName, "Foo")
is.Equal(t1.MyAge, int64(42))
})
t.Run("ImplicitCastToInt", func(t *testing.T) {
is := is.New(t)
t1 := struct{ Age int }{}
Expand Down

0 comments on commit 1c5741f

Please sign in to comment.