嵌套结构体导出excel的实现方式

简介: 嵌套结构体导出excel的实现方式

前言


之前写过 go 导出excel并返回前端下载, 有个朋友用我封装方法的时候出现了问题, 结构体的部分字段没有写入, 这个问题是怎么个操作呢


问题


他的结构体是嵌套了另一个匿名结构体, 即:

type A struct {
    Name string
  }
  param := struct {
    Id int
    A
  }{1, A{"test"}}

然后导致 WriteStruct 方法解析不出来, WriteStruct:


// Writes a struct to row r. Accepts a pointer to struct type 'e',
// and the number of columns to write, `cols`. If 'cols' is < 0,
// the entire struct will be written if possible. Returns -1 if the 'e'
// doesn't point to a struct, otherwise the number of columns written
func (r *Row) WriteStruct(e interface{}, cols int) int {
  if cols == 0 {
    return cols
  }
  v := reflect.ValueOf(e).Elem()
  if v.Kind() != reflect.Struct {
    return -1 // bail if it's not a struct
  }
  n := v.NumField() // number of fields in struct
  if cols < n && cols > 0 {
    n = cols
  }
  var k int
  for i := 0; i < n; i, k = i+1, k+1 {
    f := v.Field(i)
    switch t := f.Interface().(type) {
    case time.Time:
      cell := r.AddCell()
      cell.SetValue(t)
    case fmt.Stringer: // check Stringer first
      cell := r.AddCell()
      cell.SetString(t.String())
    case sql.NullString: // check null sql types nulls = ''
      cell := r.AddCell()
      if cell.SetString(``); t.Valid {
        cell.SetValue(t.String)
      }
    case sql.NullBool:
      cell := r.AddCell()
      if cell.SetString(``); t.Valid {
        cell.SetBool(t.Bool)
      }
    case sql.NullInt64:
      cell := r.AddCell()
      if cell.SetString(``); t.Valid {
        cell.SetValue(t.Int64)
      }
    case sql.NullFloat64:
      cell := r.AddCell()
      if cell.SetString(``); t.Valid {
        cell.SetValue(t.Float64)
      }
    default:
      switch f.Kind() {
      case reflect.String, reflect.Int, reflect.Int8,
        reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64, reflect.Float32:
        cell := r.AddCell()
        cell.SetValue(f.Interface())
      case reflect.Bool:
        cell := r.AddCell()
        cell.SetBool(t.(bool))
      default:
        k-- // nothing set so reset to previous
      }
    }
  }
  return k
}


可以看到里面只反射了第一层, 如果里面有嵌套结构的话就不会继续遍历, 所以导致嵌套结构体的字段值没有写入到excel中


解决


通过查看 WriteStruct 方法的源码文件我们可以看到里面还有一个可用方法 WriteSlice:

// Writes an array to row r. Accepts a pointer to array type 'e',
// and writes the number of columns to write, 'cols'. If 'cols' is < 0,
// the entire array will be written if possible. Returns -1 if the 'e'
// doesn't point to an array, otherwise the number of columns written.
func (r *Row) WriteSlice(e interface{}, cols int) int {
  if cols == 0 {
    return cols
  }
  // make sure 'e' is a Ptr to Slice
  v := reflect.ValueOf(e)
  if v.Kind() != reflect.Ptr {
    return -1
  }
  v = v.Elem()
  if v.Kind() != reflect.Slice {
    return -1
  }
  // it's a slice, so open up its values
  n := v.Len()
  if cols < n && cols > 0 {
    n = cols
  }
  var setCell func(reflect.Value)
  setCell = func(val reflect.Value) {
    switch t := val.Interface().(type) {
    case time.Time:
      cell := r.AddCell()
      cell.SetValue(t)
    case fmt.Stringer: // check Stringer first
      cell := r.AddCell()
      cell.SetString(t.String())
    case sql.NullString:  // check null sql types nulls = ''
      cell := r.AddCell()
      if cell.SetString(``); t.Valid {
        cell.SetValue(t.String)
      }
    case sql.NullBool:
      cell := r.AddCell()
      if cell.SetString(``); t.Valid {
        cell.SetBool(t.Bool)
      }
    case sql.NullInt64:
      cell := r.AddCell()
      if cell.SetString(``); t.Valid {
        cell.SetValue(t.Int64)
      }
    case sql.NullFloat64:
      cell := r.AddCell()
      if cell.SetString(``); t.Valid {
        cell.SetValue(t.Float64)
      }
    default:
      switch val.Kind() { // underlying type of slice
      case reflect.String, reflect.Int, reflect.Int8,
        reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64, reflect.Float32:
        cell := r.AddCell()
        cell.SetValue(val.Interface())
      case reflect.Bool:
        cell := r.AddCell()
        cell.SetBool(t.(bool))
      case reflect.Interface:
        setCell(reflect.ValueOf(t))
      }
    }
  }
  var i int
  for i = 0; i < n; i++ {
    setCell(v.Index(i))
  }
  return i
}

这样的话我们自己写一套反射规则把结构体的值映射出来就好了

// StructValueToSlice 结构体值转入slice
func StructValueToSlice(val interface{}) (data []interface{}) {
  // 判断是否为指针
  //var t reflect.Type
  var v reflect.Value
  if reflect.ValueOf(val).Type().Kind() == reflect.Struct {
    v = reflect.ValueOf(val)
    //t = reflect.TypeOf(val)
  } else {
    v = reflect.Indirect(reflect.ValueOf(val))
    //t = reflect.TypeOf(val).Elem()
  }
  for i := 0; i < v.NumField(); i++ {
    //判断是否是嵌套结构
    if v.Field(i).Type().Kind() == reflect.Struct {
      structField := v.Field(i).Type()
      for j := 0; j < structField.NumField(); j++ {
        //fmt.Println(fmt.Sprintf("%s %s = %v ", structField.Field(j).Name, structField.Field(j).Type, v.Field(i).Field(j).Interface()))
        data = append(data, v.Field(i).Field(j).Interface())
      }
    } else {
      //fmt.Println(fmt.Sprintf("%s %s = %v ", t.Field(i).Name, t.Field(i).Type, v.Field(i).Interface()))
      data = append(data, v.Field(i).Interface())
    }
  }
  return
}
// RecursionStructValueToSlice 递归多层嵌套结构体的值转入slice
func RecursionStructValueToSlice(val interface{}, v reflect.Value, data *[]interface{}) {
  // 判断是否为指针
  if val == nil {
  } else if reflect.ValueOf(val).Type().Kind() == reflect.Struct {
    v = reflect.ValueOf(val)
  } else {
    v = reflect.Indirect(reflect.ValueOf(val))
  }
  for i := 0; i < v.NumField(); i++ {
    //判断是否是嵌套结构
    if v.Field(i).Type().Kind() == reflect.Struct {
      RecursionStructValueToSlice(nil, v.Field(i), data)
    } else {
      *data = append(*data, v.Field(i).Interface())
    }
  }
  return
}

下面是单元测试

func TestStructValueToSlice(t *testing.T) {
  type A struct {
    Name string
  }
  param := struct {
    Id int
    A
  }{1, A{"test"}}
  t.Log(StructValueToSlice(param))
}
func TestRecursionStructValueToSlice(t *testing.T) {
  type A struct {
    Age int
  }
  type B struct {
    Name string
    A
  }
  param := struct {
    Id int
    B
  }{1, B{"test", A{11}}}
  data := make([]interface{}, 0)
  RecursionStructValueToSlice(param, reflect.Value{}, &data)
  t.Log(data)
}

这样的话就可以了, 插入之前先走下映射获取到值的结构体切片, 然后再用 WriteSlice 方法写入就可以了


优化


看 WriteSlice 方法可以看出来它里面也是反射获取slice的值, 然后 r.AddCell().cell.SetValue(t) 方法插进去了, 所以想提升效率的话可以直接在反射的方法里直接将值写入行就行了, 省了一步反射的过程

目录
相关文章
|
1月前
|
前端开发
实现Excel文件和其他文件导出为压缩包,并导入
实现Excel文件和其他文件导出为压缩包,并导入
30 1
|
1月前
|
数据格式 UED
记录一次NPOI库导出Excel遇到的小问题解决方案
【11月更文挑战第16天】本文记录了使用 NPOI 库导出 Excel 过程中遇到的三个主要问题及其解决方案:单元格数据格式错误、日期格式不正确以及合并单元格边框缺失。通过自定义单元格样式、设置数据格式和手动添加边框,有效解决了这些问题,提升了导出文件的质量和用户体验。
176 3
|
1月前
|
Java API Apache
|
1月前
|
存储 Java API
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
70 4
|
2月前
|
JavaScript 前端开发 数据处理
Vue导出el-table表格为Excel文件的两种方式
Vue导出el-table表格为Excel文件的两种方式
110 6
|
3月前
|
SQL C# 数据库
EPPlus库的安装和使用 C# 中 Excel的导入和导出
本文介绍了如何使用EPPlus库在C#中实现Excel的导入和导出功能。首先,通过NuGet包管理器安装EPPlus库,然后提供了将DataGridView数据导出到Excel的步骤和代码示例,包括将DataGridView转换为DataTable和使用EPPlus将DataTable导出为Excel文件。接着,介绍了如何将Excel数据导入到数据库中,包括读取Excel文件、解析数据、执行SQL插入操作。
EPPlus库的安装和使用 C# 中 Excel的导入和导出
|
2月前
|
easyexcel Java UED
SpringBoot中大量数据导出方案:使用EasyExcel并行导出多个excel文件并压缩zip后下载
在SpringBoot环境中,为了优化大量数据的Excel导出体验,可采用异步方式处理。具体做法是将数据拆分后利用`CompletableFuture`与`ThreadPoolTaskExecutor`并行导出,并使用EasyExcel生成多个Excel文件,最终将其压缩成ZIP文件供下载。此方案提升了导出效率,改善了用户体验。代码示例展示了如何实现这一过程,包括多线程处理、模板导出及资源清理等关键步骤。
|
2月前
|
前端开发 JavaScript
💥【exceljs】纯前端如何实现Excel导出下载和上传解析?
本文介绍了用于处理Excel文件的库——ExcelJS,相较于SheetJS,ExcelJS支持更高级的样式自定义且易于使用。表格对比显示,ExcelJS在样式设置、内存效率及流式操作方面更具优势。主要适用于Node.js环境,也支持浏览器端使用。文中详细展示了如何利用ExcelJS实现前端的Excel导出下载和上传解析功能,并提供了示例代码。此外,还提供了在线调试的仓库链接和运行命令,方便读者实践。
444 5
|
2月前
|
前端开发 JavaScript Java
导出excel的两个方式:前端vue+XLSX 导出excel,vue+后端POI 导出excel,并进行分析、比较
这篇文章介绍了使用前端Vue框架结合XLSX库和后端结合Apache POI库导出Excel文件的两种方法,并对比分析了它们的优缺点。
853 0
|
3月前
|
存储 Java
java的Excel导出,数组与业务字典匹配并去掉最后一个逗号
java的Excel导出,数组与业务字典匹配并去掉最后一个逗号
62 2