首页 > 解决方案 > 当标题数量不固定时使用 CsvHelper 创建 csv 文件

问题描述

我有 json 对象的列表。每个 json 对象都具有公共属性和该 json 对象独有的一些属性。像下面的示例

{"FirstName":"foo","LastName":"bar"}
{"FirstName":"jhon","LastName":"dow"}
{"FirstName":"james","LastName":"smith","Age":26}
{"LastName":"jones","Age":30, "Address":"1234 Test Drive"}

我想使用CsvHelper创建 CSV 报告,其中每个属性都是标题。当属性不存在时,该列的值应为空

这是我当前的实现,当然没有考虑额外的属性

var records = new List<dynamic>();
foreach (var jObj in result)
{
   var record = new ExpandoObject();
   foreach (var property in jObj)
   {
      record.TryAdd(property.Key, property.Value.ToString());
   }

   records.Add(record);
}

using (var writer = new StreamWriter(filePath))
{
    using (var csv = new CsvWriter(writer))
    {
        csv.WriteRecords(records);
    }
}

所以在上面的例子中,结果 csv 应该有 4 个 headers FirstName, LastName,AgeAddress

请注意,记录的数量可能是数千

下面的更新 1
是我的临时解决方案,直到我找到更好的方法来做到这一点而无需循环两次

    [Fact]
    public async Task CreateCSVFromJObjects()
    {
        // arrange
        var list = new JObject[]
        {
            JObject.FromObject(new { FirstName = "foo",LastName = "bar" }),
            JObject.FromObject(new { FirstName = "john",LastName = "doe" }),
            JObject.FromObject(new { FirstName = "james",LastName = "smith", Age = 26 }),
            JObject.FromObject(new { LastName = "bar", Address = "123 Test Drive" })
        }.ToList();


        //act
        var headers = new HashSet<string>();
        foreach (var j in list)
        {
            foreach (var p in j)
            {
                if (!headers.Contains(p.Key))
                {
                    headers.Add(p.Key);
                }
            }
        }


        using (var writer = new StreamWriter("C:\\temp\\test.csv"))
        {
            using (var csv = new CsvWriter(writer))
            {
                foreach (var header in headers)
                {
                    csv.WriteField(header);
                }
                await csv.NextRecordAsync();

                foreach (var jObj in list)
                {
                    foreach (var header in headers)
                    {
                        var token = jObj[header];
                        if (token != null)
                        {
                            csv.WriteField(token.ToString());
                        }
                        else
                        {
                            csv.WriteField("");
                        }
                    }

                    await csv.NextRecordAsync();
                }
            }
        }
    }

标签: c#.net.net-corecsvhelper

解决方案


如果结果不必与处理一起实时流式传输,则在与流写入相同的循环内创建和维护标头集和标头列表可能更有效,跳过标头写入首先是流。然后您可以构建一个新流,写入标题,然后将原始流复制到其中。

是否可以使用内存流进行中间写入取决于您的内存需求。

每个请求的伪代码。这实际上只是对现有代码的一个小重新排列。它需要更少的循环,但需要更多的内存或磁盘。问题的性质需要权衡。

Stream intermediate_stream // memory or file
List headers // only add a set if List is a bottleneck (1000s of properties); list required to maintain ordering
foreach ( obj in list ) :
  foreach ( prop_name in obj.props )
    headers.add_if_unique(prop_name)
  foreach ( name in headers ) // to preserve ordering in output
   intermediate_stream.write(obj.prop_value(name))

Stream final_stream
final_stream.write(headers)
intermediate_stream.copy_to(final_stream)

推荐阅读