将字符串CSV转换为对象列表

6
我有以下字符串。
const string csv = "Foo1,Foo2,Foo3,Foo4,Foo5,Foo6,Ping Pong\n" +
                   "2016-02-29,1437.530029,1445.839966,1433.77002,1436.930054,34016300,1436.930054\n" +
                   "2016-02-25,1431.439941,1431.439941,1421.280029,1426.97998,78076500,1426.97998\n" +
                   "2016-02-24,1430.459961,1432.430054,1417.449951,1419.790039,29049900,1419.790039\n";

我该如何将其转换为 List<Model> 呢?
public class Model
{
    public string Foo1 { get; set; }
    public string Foo2 { get; set; }
    public string Foo3 { get; set; }
    public string Foo4 { get; set; }
    public string Foo5 { get; set; }
    public string Foo6 { get; set; }
    public string Ping_Pong { get; set; }
}

注意原始CSV文件中的头部信息

我尝试使用CsvHelper,但因为它需要流而不是字符串,所以没有成功。我还尝试转换解析,但失败了。

编辑对我来说是否要使用CsvHelper无关紧要,最终我想将CSV文件转换为List<Model>

我该如何做到这一点?


为什么不在这里实现自定义解析逻辑呢? - Tamas Ionut
或者您可以将字符串转换为流,非常容易:var ms = new MemoryStream(new UTF8Encoding.GetBytes(csv)); - Kevin
有多种方法可以将一个 string 转换为 Stream。https://dev59.com/o3I-5IYBdhLWcg3wZ3UR - juharr
我建议你不要放弃csvhelper - 我在使用它时取得了很多成功。它可以接受TextReader,你可以通过使用StringReader将字符串暴露给读取器来获取你的字符串:var myReader = new StringReader(thecsvString)。 - JMarsch
3个回答

16
您可以使用csv变量创建一个StringReader:
var textReader = new StringReader(csv);

var csvr = new CsvReader(textReader);
var records = csvr.GetRecords<Model>();

如果您想要自己的解析器:

var lines = csv.Split(new char[] {'\n'}, StringSplitOptions.RemoveEmptyEntries).Skip(1);
List<Model> models = new List<Model>();

foreach (var item in lines)
{
    var values = item.Split(',');
    var model = new Model
    {
        Foo1 = values[0],
        Foo2 = values[1],
        Foo3 = values[2],
        Foo4 = values[3],
        Foo5 = values[4],
        Foo6 = values[5],
        Ping_Pong = values[6],
    };

    models.Add(model);
}

编辑:

使用 CsvHelper 解决标题问题,需要创建一个映射配置类来指定标题和属性之间的映射关系:

public class ModelMap : CsvClassMap<Model>
{
    public ModelMap()
    {
        Map(m => m.Foo1);
        Map(m => m.Foo2);
        Map(m => m.Foo3);
        Map(m => m.Foo4);
        Map(m => m.Foo5);
        Map(m => m.Foo6);
        Map(m => m.Ping_Pong).Name("Ping Pong");
    }
}

使用方法如下:

var textReader = new StringReader(csv);

var csvr = new CsvReader(textReader);
csvr.Configuration.RegisterClassMap<ModelMap>();

var records = csvr.GetRecords<Model>();

这个很完美地工作了,我的问题是如何将标题分配给 Model 类中正确的属性?我更喜欢而不是读取 [x] 的位置。 - user829174
你可以直接使用 csv.Split('\n') 以及 Split(params char[] separators) 重载方法来实现,因为对于 OP 的示例来说,你并不真正需要移除空条目。 - juharr
1
不知怎么的,当我测试你的第二个答案时,对我来说并没有起作用。var records = csvr.GetRecords<StockModel>(); 这一行代码给我返回了null。 - user829174
@user829174:返回null还是对象属性为null? - Arturo Menchaca
尝试使用以下代码:var records = csvr.GetRecords<Model>().ToList(); 显式地将其转换为列表。 - Arturo Menchaca
显示剩余5条评论

0
问题在于您的数据集(csv字符串)缺少一列(您指定了7列,但Ping Pong列在csv中缺失)。默认行为将是抛出异常,但您可以设置配置选项以忽略缺失的列:

以下是可工作的代码:

var config = new CsvConfiguration();
// setting this will cause the missing Ping Pong field to be ignored
config.WillThrowOnMissingField = false;

// we wrap your string in a StringReader to make it accessible to CsvReader
var reader = new CsvReader(new StringReader(csv), config);
var records = reader.GetRecords<Model>().ToList();
records.Dump();

我确实有“乒乓”数据,在我的问题中进行了双重检查。我的标题中有7个值(其中乒乓之间有空格),而数据中也有7个值。 - user829174
@user829174 你说得对,我数错了。我将你的标题从“Ping Pong”改名为“Ping_Pong”,以便与你的模型匹配,现在所有7列都可以解析。 - JMarsch

-1

要做到您所要求的,您可以使用以下代码:

var modelStrings = csv.Split('\n').Skip(1);
var models = new List<Model>();
foreach(string s in modelStrings)
{
    var props = s.Split(',');
    models.Add(new Model(props[0],props[1],props[2],props[3],props[4],props[5],props[6]));

}

但是需要提醒的是:这可能会很慢,您可能需要添加更广泛的逻辑来实例化新模型,以应对 CSV 格式可能发生的变化。

编辑:

为了澄清这个操作的作用,它首先通过跳过第一行将 CSV 字符串按新行分割。然后使用该列表中的每个字符串并按逗号分割它们,以获取要使用 Model 类实例化的值列表(模型属性)。


不确定它是否有效,'foreach(string s in models)'是什么? - user829174
糟糕,我改变了一个变量名,让我编辑我的答案。 - Gordon Allocman
你的第一个分割也会在“Ping Pong”中的空格处进行分割。 - juharr
我喜欢使用像csvreader这样的库而不是手动解析的一个原因是,读取器将处理以Excel的csv格式存储的数据。这可能对OP的使用有所影响,但这里有一个例子:如果其中一个数据值是逗号(因此您想要该逗号,它不是分隔符),Excel会将该值放在引号中,并忽略引号内的任何逗号。 CsvHelper已经内置了所有这些解析逻辑。只是需要知道这一点--对于OP的需求可能过度,但也可能不是。 - JMarsch
@juharr 好的,我相信我在Split文档上误解了信息,我已经更新了它,所以不应该再有问题了。 - Gordon Allocman
由于您使用的Split重载是Split(params char[] seperators),因此您不必创建一个数组,所以csv.Split('\n')会做同样的事情。 - juharr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接