diff --git a/schema/schema.go b/schema/schema.go index 8f7dcca..862d117 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -260,20 +260,41 @@ func getStructFields(out interface{}) ([]structField, error) { for i := 0; i < outt.NumField(); i++ { fieldValue := outv.Field(i) if fieldValue.CanSet() { // Only consider exported fields. - // If it is an struct or a struct pointer - if fieldValue.Kind() == reflect.Struct || - (fieldValue.Kind() == reflect.Ptr && fieldValue.Type().Elem().Kind() == reflect.Struct) { - if fieldValue.Kind() == reflect.Ptr { - fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + switch { + // Special case on datetime fields, wich is a first-class schema + // type, represented as a struct with all fields unexported. + case fieldValue.Type() == reflect.TypeOf(time.Time{}): + fields = append(fields, structField{outt.Field(i), fieldValue}) + + // It it is a struct, deep dive on fields recursively. + case fieldValue.Kind() == reflect.Struct: + newF, err := getStructFields(reflect.Indirect(fieldValue).Addr().Interface()) + if err != nil { + return nil, err + } + fields = append(fields, newF...) + + // If it is a pointer. + case fieldValue.Kind() == reflect.Ptr: + // Allocate memory to it. + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + + // If it is not a struct, simply add to the list. + if fieldValue.Type().Elem().Kind() != reflect.Struct { + fields = append(fields, structField{outt.Field(i), fieldValue}) + break } + + // It it is a struct, deep dive on fields recursively. newF, err := getStructFields(reflect.Indirect(fieldValue).Addr().Interface()) if err != nil { return nil, err } fields = append(fields, newF...) - continue + + default: + fields = append(fields, structField{outt.Field(i), fieldValue}) } - fields = append(fields, structField{outt.Field(i), fieldValue}) } } return fields, nil diff --git a/schema/schema_test.go b/schema/schema_test.go index de5c2b4..cec0d58 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "strings" "testing" + "time" "github.com/frictionlessdata/tableschema-go/table" "github.com/matryer/is" @@ -322,6 +323,17 @@ func TestSchema_Cast(t *testing.T) { is.Equal(t1.MyName, "Foo") is.Equal(t1.E.MyAge, int64(42)) }) + t.Run("TimeField", func(t *testing.T) { + is := is.New(t) + t1 := struct { + T time.Time `tableheader:"T"` + }{} + s := Schema{Fields: []Field{{Name: "T", Type: DateTimeType, Format: "%Y-%m-%dT%H:%M:%S.fZ"}}} + is.NoErr(s.CastRow([]string{"2021-11-28T14:51:05.35811Z"}, &t1)) + + want, _ := time.Parse(time.RFC3339Nano, "2021-11-28T14:51:05.35811Z") + is.Equal(t1.T, want) + }) t.Run("StructPointerField", func(t *testing.T) { is := is.New(t) type EmbededT struct {