より良いエンジニアを目指して

1日1つ。良くなる!上手くなる!

CsvHelperでマッピングなしでパースして読み書きしたい時

C#csvをパースする方法は、いくつかありますが特に制約が無いのであればCsvHelperがオススメです。

https://github.com/JoshClose/CsvHelper

VisualBasic.TextFieldParserより遥かに優れた読み込み速度を期待できます。GitHub - rimever/CsvParserBenchmarks: Csvのパーサーライブラリの比較検証のためのリポジトリです。

ただ、このCsvHelperはマッピングあり前提で作られているせいか、やや柔軟性に欠けます。

そこでマッピングなしで読み書きする方法を紹介します。

マッピングなしの読み込み

Dicitonary型に変換する方法

下記の記事で紹介されてますが、

stackoverflow.com

            using (var reader = new CsvReader(new StreamReader(csvFilePath, encoding)))
            {
                while (reader.Read())
                {
                    
                var dictionary = reader.GetRecord<dynamic>() as IDictionary<string, object>;
                var stringBuilder = new StringBuilder();
                foreach (var pair in dictionary)
                {
                    stringBuilder.Append($"{pair.Key}->{pair.Value},");
                }
                Console.WriteLine(stringBuilder.ToString());
                }
            }

とします。

ヘッダーなしの場合

CsvReaderにヘッダーなしの設定を渡せばOKです。

// ヘッダーなしを設定に渡す
using (var reader = new CsvReader(new StreamReader(csvFilePath, encoding))
            {
                Configuration = { HasHeaderRecord = false}
            })
            {
                while (reader.Read())
                {
                    
                var dictionary = reader.GetRecord<dynamic>() as IDictionary<string, object>;
                var stringBuilder = new StringBuilder();
                foreach (var pair in dictionary)
                {
                    stringBuilder.Append($"{pair.Key}->{pair.Value},");
                }
                Console.WriteLine(stringBuilder.ToString());
                }
            }

すると

Field1->p,Field2->x,Field3->s,Field4->n,Field5->t,Field6->p,Field7->f,Field8->c,Field9->n,Field10->k,Field11->e,Field12->e,Field13->s,Field14->s,Field15->w,Field16->w,Field17->p,Field18->w,Field19->o,Field20->p,Field21->k,Field22->s,Field23->u,

のような辞書型となります。

Context.Recordから取得する

これでいいとは思いますが、ヘッダーあり+辞書型で取得する場合は、カラム列の並び順に取得できない(カラム列の名前順になる)という問題点もあります。

もう一つの手段としては、CsvReaderのContext.Recordプロパティから取得するという方法です。

純粋に、string配列ですのでカラムの何番目という取得方法であればシンプルに扱えます。

            using (var reader = new CsvReader(new StreamReader(csvFilePath, encoding))
            {
                Configuration = { HasHeaderRecord = false}
            })
            {
                while (reader.Read())
                {
                    Console.WriteLine(string.Join(",", reader.Context.Record));
                }
            }

マッピングなしの書き込み

List<List<string>> lists = new List<List<string>>
            {
                new List<string>() {"a" + Environment.NewLine + "b", @"c""d", "e"}, new List<string>() {"e", "f", "g,h"}
            };
            using (var writer = new CsvWriter(new StreamWriter(path,false,Encoding.UTF8))
            {
                Configuration =
                {
                    ShouldQuote = (s, context) => true
                }
            })
            {
                foreach (var list in lists)
                {    
                    writer.WriteField(list.ToArray());
                    writer.NextRecord();
                }
            }

その他

職場で聞いた話では、CsvHelperは読み込みが厳しいので、緩い書き方だと失敗することがある。

修正履歴

  • (2/20)書き込みについても記載し、タイトルを修正しました。