我在.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 delimiter
&amp; string enclosure
和csv上的确切列数未知,但包含我需要的数据的列索引是已知的。
PartNumb,InStock,PrGroup
"A-X-1230",Y,103
"B-DD-1231",Y,103
其中; column delimiter
= ,
和string enclosure
= "
我需要的数据:列索引0
(PartNumb)和列索引1
(InStock)
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 delimiter
,string enclosure
和column indexes
已知)的最佳方法是什么?我还需要在csv上跳过第一行的选项(有时csv包含标题行,有时它们不会)。
答案 0 :(得分:3)
答案取决于你想要的:
如果您不知道允许哪些分隔符,则您遇到了麻烦:A&#39; A&#39;分隔符?是&#39; B&#39;分隔符?让我们假设您认为有一组字符适合作为您要解析的实际CSV流的分隔符。
您可以使用String.Replace(char,char)为每个接受的分隔符执行此操作,或使用正则表达式
如果您只需要某些列,请创建一个地图,告诉CsvHelper哪个列必须映射到哪个目的地。
示例:如果您需要映射列&#34; MyColumn&#34; to 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
答案 1 :(得分:2)
这似乎有效,使用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 :(得分:1)
通过更多努力,您还可以使用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 =”,“,添加另一个类&amp; CsvClassMap&lt;&gt;实现并在另一个CsvReader中配置。
答案 3 :(得分:1)
有一个包升级,新语法现在是:
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 :(得分:0)
CsvReader csv = new CsvReader(new StreamReader(stream), true, ';')
Configuration.Delimiter它不再起作用,现在您必须在CsvReader初始化中将分隔符作为参数传递