@@ -296,22 +296,23 @@ func renderTable(out any, sort string, headers table.Row, filterColumns []string
296296// returned. If the table tag is malformed, an error is returned.
297297//
298298// The returned name is transformed from "snake_case" to "normal text".
299- func parseTableStructTag (field reflect.StructField ) (name string ,defaultSort ,noSortOpt ,recursive ,skipParentName bool ,err error ) {
299+ func parseTableStructTag (field reflect.StructField ) (name string ,defaultSort ,noSortOpt ,recursive ,skipParentName , emptyNil bool ,err error ) {
300300tags ,err := structtag .Parse (string (field .Tag ))
301301if err != nil {
302- return "" ,false ,false ,false ,false ,xerrors .Errorf ("parse struct field tag %q: %w" ,string (field .Tag ),err )
302+ return "" ,false ,false ,false ,false ,false , xerrors .Errorf ("parse struct field tag %q: %w" ,string (field .Tag ),err )
303303}
304304
305305tag ,err := tags .Get ("table" )
306306if err != nil || tag .Name == "-" {
307307// tags.Get only returns an error if the tag is not found.
308- return "" ,false ,false ,false ,false ,nil
308+ return "" ,false ,false ,false ,false ,false , nil
309309}
310310
311311defaultSortOpt := false
312312noSortOpt = false
313313recursiveOpt := false
314314skipParentNameOpt := false
315+ emptyNilOpt := false
315316for _ ,opt := range tag .Options {
316317switch opt {
317318case "default_sort" :
@@ -326,12 +327,14 @@ func parseTableStructTag(field reflect.StructField) (name string, defaultSort, n
326327// make sure the child name is unique across all nested structs in the parent.
327328recursiveOpt = true
328329skipParentNameOpt = true
330+ case "empty_nil" :
331+ emptyNilOpt = true
329332default :
330- return "" ,false ,false ,false ,false ,xerrors .Errorf ("unknown option %q in struct field tag" ,opt )
333+ return "" ,false ,false ,false ,false ,false , xerrors .Errorf ("unknown option %q in struct field tag" ,opt )
331334}
332335}
333336
334- return strings .ReplaceAll (tag .Name ,"_" ," " ),defaultSortOpt ,noSortOpt ,recursiveOpt ,skipParentNameOpt ,nil
337+ return strings .ReplaceAll (tag .Name ,"_" ," " ),defaultSortOpt ,noSortOpt ,recursiveOpt ,skipParentNameOpt ,emptyNilOpt , nil
335338}
336339
337340func isStructOrStructPointer (t reflect.Type )bool {
@@ -358,7 +361,7 @@ func typeToTableHeaders(t reflect.Type, requireDefault bool) ([]string, string,
358361noSortOpt := false
359362for i := 0 ;i < t .NumField ();i ++ {
360363field := t .Field (i )
361- name ,defaultSort ,noSort ,recursive ,skip ,err := parseTableStructTag (field )
364+ name ,defaultSort ,noSort ,recursive ,skip ,_ , err := parseTableStructTag (field )
362365if err != nil {
363366return nil ,"" ,xerrors .Errorf ("parse struct tags for field %q in type %q: %w" ,field .Name ,t .String (),err )
364367}
@@ -435,16 +438,22 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
435438for i := 0 ;i < val .NumField ();i ++ {
436439field := val .Type ().Field (i )
437440fieldVal := val .Field (i )
438- name ,_ ,_ ,recursive ,skip ,err := parseTableStructTag (field )
441+ name ,_ ,_ ,recursive ,skip ,emptyNil , err := parseTableStructTag (field )
439442if err != nil {
440443return nil ,xerrors .Errorf ("parse struct tags for field %q in type %T: %w" ,field .Name ,val ,err )
441444}
442445if name == "" {
443446continue
444447}
445448
446- // Recurse if it's a struct.
447449fieldType := field .Type
450+
451+ // If empty_nil is set and this is a nil pointer, use a zero value.
452+ if emptyNil && fieldVal .Kind ()== reflect .Pointer && fieldVal .IsNil () {
453+ fieldVal = reflect .New (fieldType .Elem ())
454+ }
455+
456+ // Recurse if it's a struct.
448457if recursive {
449458if ! isStructOrStructPointer (fieldType ) {
450459return nil ,xerrors .Errorf ("field %q in type %q is marked as recursive but does not contain a struct or a pointer to a struct" ,field .Name ,fieldType .String ())
@@ -467,7 +476,7 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
467476}
468477
469478// Otherwise, we just use the field value.
470- row [name ]= val . Field ( i ) .Interface ()
479+ row [name ]= fieldVal .Interface ()
471480}
472481
473482return row ,nil