【问题标题】:Parsing csv files with specific column delimiter & string enclosure using the CsvHelper library使用 CsvHelper 库解析具有特定列分隔符和字符串附件的 csv 文件
【发布时间】:2016-02-26 12:51:05
【问题描述】:

我在我的 .NET C# 项目中使用这个 http://joshclose.github.io/CsvHelper/ 很棒的库来满足我的 CSV 解析要求。

如果我有一个如下所示的 CSV 文件:

SupplierSku,MappedSageSku
EG1234,EGCD1234
EG4567,EG-XZ567

我通常这样创建一个 DTO 类:

public class SkuMapping
{
    public string SupplierSku { get; set; }
    public string MappedSageSku { get; set; }
}

并像这样解析 csv 文件:

// Open & parse selected csv file
var csvReader = new CsvReader(File.OpenText(selectSkuMapping.Text));
var skuMappings = csvReader.GetRecords<SkuMapping>();

// Do something with each row
foreach (SkuMapping skuMapping in skuMappings)
{
    // ...
}

这对于预定义/结构化的 CSV 文件非常有用。

我现在需要解析任意 CSV 文件,其中可能包含各种 column delimiterstring enclosure 并且 csv 上的确切列数未知,但包含我需要的数据的列索引是已知的。

示例 #1

PartNumb,InStock,PrGroup
"A-X-1230",Y,103
"B-DD-1231",Y,103

在哪里; column delimiter = ,string enclosure = "

我需要的数据:列索引0 (PartNumb) 和列索引1 (InStock)

示例 #2

SupplierSku,CatIds,StockStatus,Active
%ADA-BB-124%|4,5,1|%AV%|1
%XAS-E4-S11%|97,41,65|%OS%|0

在哪里; column delimiter = |string enclosure = %

我需要的数据:列索引0 (SupplierSku) 和列索引2 (StockStatus)


鉴于上述情况,使用 CsvHelper 库解析任意 csv 文件的最佳方法是什么(已知 column delimiterstring enclosurecolumn indexes)?我还需要跳过 csv 第一行的选项(有时 csv 包含标题行,有时它们不包含)。

【问题讨论】:

  • 为什么需要使用助手?您当然可以逐行阅读,按分隔符分割并根据需要挑选索引条目的数量?
  • 帮助库的存在是为了防止你重新发明轮子。我想既然我已经在我的应用程序中广泛使用了这个库,我会再次使用它来解析任意 csv 文件。这是否意味着CsvHelper 无法满足我的要求?我需要手动解析文件吗?
  • 根据文档,您可以将其用作阅读器。
  • 当您需要快速阅读 CSV 文件时,我发现 lumenworks 很棒。小心使用诸如 split() 之类的字符串操作,因为它们可能导致 outOfMemoryExceptions。见这里:stackoverflow.com/questions/32327971/…
  • Csv 并不那么容易通过行拆分来解析。健壮的代码需要真正的实现。

标签: c# csv


【解决方案1】:

这似乎工作,使用 CsvHelper:

var textToParse = @"SupplierSku,CatIds,StockStatus,Active
%ADA-BB-124%|4,5,1|%AV%|1
%XAS-E4-S11%|97,41,65|%OS%|0";

string supplierSku;
string stockStatus;

using (var stringReader = new StringReader(textToParse))
{
    using (var reader = new CsvReader(stringReader))
    {
        reader.Configuration.Delimiter = ",";
        reader.Configuration.HasHeaderRecord = true; // If there is no header, set to false.

        while (reader.Read())
        {
            supplierSku = reader.GetField("SupplierSku"); // Or reader.GetField(0)
            stockStatus = reader.GetField("StockStatus"); // Or reader.GetField(2)

            Console.WriteLine($"SKU: {supplierSku}; Status: {stockStatus}");
        }
    }
}

但是,它不会自动修剪/删除引号字符 - 您可以使用 Trim()Substring() 自己轻松完成。需要更多的手动操作,但仍然比手动操作更容易。

【讨论】:

  • 这是完美的,正是我所追求的。谢谢。
【解决方案2】:

答案取决于你想要什么:

  • 您知道在开始解析之前允许使用哪些分隔符吗?

如果您不知道允许使用哪些分隔符,您就有麻烦了:'A' 是分隔符吗? 'B' 是分隔符吗?假设有一组您认为适合作为您要解析的实际 CSV 流的分隔符的字符。

  1. 用相同的分隔符替换所有出现的分隔符,例如';'
  2. 您可以对每个接受的分隔符使用 String.Replace(char, char) 来执行此操作,或使用正则表达式

    • 是否要按名称的数量选择列?

如果您只需要某些列,请创建一个映射,告诉 CsvHelper 哪个列必须映射到哪个目标。

示例:如果您需要将列“MyColumn”映射到 Property YourProperty,请创建一个映射:

private sealed class MyCsvConverterMap : CsvClassMap<MyDestinationType>
{
    public MyCsvConverterMap()
    {
        Map(item => item.YourProperty).Name("MyColumn");
        // map all properties in your destination to a column
    }
}

using (TextReader txtReader = new StringReader(...))
{
    CsvReader csvReader = new CsvReader(txtReader);
    csvReader.Configuration.Delimiter = ";";
    csvReader.Configuration.HasHeaderRecord = true;
    csvReader.Configuration.RegisterClassMap(new MyCsvConverterMap());

    while (csvReader.Read())
    {
         MyDestinationType convertedRecord = csvReader.GetRecord<MyDestinationType>();
         ...

加法

也可以按列号映射,而不是按列名映射。查看各种地图的解释: CsvHelper Getting started

【讨论】:

  • 这看起来确实很有趣。根据我原来的问题;对于我要使用的每个 csv 文件变体,我知道 column delimiterstring enclosure 和包含我需要的数据的索引(不是列名)。鉴于此,如何使用 fluent mapping 来实现 csv 解析?
【解决方案3】:

有一个包升级,现在的新语法是:

using (var reader = new StreamReader("./myfile.csv", Encoding.UTF8))
using (var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture, delimiter: ";", encoding: Encoding.UTF8)))
{
    //do something with csv
}

【讨论】:

    【解决方案4】:

    您还可以通过CsvClassMap 创建一个类型安全的映射,并从DefaultTypeConverter 类继承来为CatIds(逗号分隔)创建一个转换器。

    这是一个适用于您的示例 #2 的示例:

    [TestClass]
    public class CsvHelperTest
    {
        [TestMethod]
        public void Test()
        {
            var textToParse = "SupplierSku,CatIds,StockStatus,Active" + Environment.NewLine;
            textToParse += "%ADA-BB-124%|4,5,1|%AV%|1" + Environment.NewLine;
            textToParse += "%XAS-E4-S11%|97,41,65|%OS%|0";
    
            using (var stringReader = new StringReader(textToParse))
            {
                using (var reader = new CsvReader(stringReader))
                {
                    reader.Configuration.Quote = '%';
                    reader.Configuration.Delimiter = "|";
                    reader.Configuration.HasHeaderRecord = true; // If there is no header, set to false.
                    reader.Configuration.RegisterClassMap<StockMap>();
    
                    foreach(var stock in reader.GetRecords<Stock>())
                    {
                        // normally do something with data, now just test
    
                        Assert.IsNotNull(stock.SupplierSku);
                        Assert.IsTrue(stock.SupplierSku.IndexOf('%') == -1, "Quotes should be stripped");
                        Assert.IsNotNull(stock.CatIds);
                        Assert.AreEqual(3, stock.CatIds.Length, "Expected 3 CatIds");
                    }
                }
            }
        }
    
        public class StockMap : CsvClassMap<Stock>
        {
            public StockMap()
            {
                Map(stock => stock.SupplierSku).Index(0);
                Map(stock => stock.CatIds).Index(1).TypeConverter<CatIdsConverter>();
                Map(stock => stock.StockStatus).Index(2);
                Map(stock => stock.Active).Index(3); // 1 is true, 0 is false
            }
        }
    
        public class Stock
        {
            public string SupplierSku { get; set; }
            public int[] CatIds { get; set; }
            public StockStatus StockStatus { get; set; }
            public bool Active { get; set; }
        }
    
        public enum StockStatus
        {
            AV, OS
        }
    
        public class CatIdsConverter : DefaultTypeConverter
        {
            public override bool CanConvertFrom(Type type)
            {
                return type == typeof(string);
            }
    
            public override object ConvertFromString(TypeConverterOptions options, string text)
            {
                if (string.IsNullOrEmpty(text))
                    return null;
    
                var catIds = text.Split(',').Select(c=> Convert.ToInt32(c)).ToArray();
                return catIds;
            }
        }
    }
    

    例如 #1 只需配置 Quote = '"', Delimiter = ",", 添加另一个类和 CsvClassMap 实现并在另一个 CsvReader 中进行配置。

    【讨论】:

      【解决方案5】:
      CsvReader csv = new CsvReader(new StreamReader(stream), true, ';')
      

      Configuration.Delimiter 它不再起作用,现在您必须在 CsvReader 初始化中将分隔符作为参数传递

      【讨论】:

        猜你喜欢
        • 2014-05-28
        • 1970-01-01
        • 1970-01-01
        • 2023-03-30
        • 2023-03-31
        • 1970-01-01
        • 1970-01-01
        • 2015-08-09
        • 1970-01-01
        相关资源
        最近更新 更多