做專案免不了遇到匯出或讀取 CSV 的需求,將物件轉成逗號分隔字串看似小菜一碟,用 C# 串字串也能搞定,但魔鬼在細節裡:字串值如包含逗號就要用雙引號包夾,遇到雙引號要置換成兩個雙引號,如果字串內容有換行符號更是讀取識別時的一大挑戰… 不管是匯出或解析 CSV 都得費不少力氣。最近發現一個處理 CSV 的強大元件-ServiceStack.Text 的 CsvSerializer!
ServiceStack是一套用於快速打造 SOA 服務的 Framework 工具組(可取代 WCF、WebAPI),強調輕巧、快速。ServiceStack.Text則是其中處理 JSON、CSV、JSV 序列化與反序列化的程式庫(在 ServiceStack 自家評測中 JSON 處理速度比 Json.NET 快三倍),而 CsvSerializer 正好可解決專案中的 CSV 需求。
以下簡單示範如何利用 ServiceStack.Text 匯出及解析 CSV。
首先使用 NuGet 安裝 ServiceStack.Text:
我用一小段程式做示範,測試對象是自訂物件陣列。
- Test1() 用 CsvSerializer.SerializeToCsv<T>() 將陣列轉為 CSV 字串。
- Test2() 用擴充方法 .FromCsv<List<T>>() 將 CSV 字串再轉回物件陣列。
- Test3() 則嘗試不定義強型別物件,將 CSV 還原回字串陣列進行客製化應用。
為了增加趣味挑戰性,當然要刻意在物件字串屬性穿插逗號、雙引號及換行,試試 ServiceStack.Text 的能耐。
using ServiceStack.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServiceStack;
using Newtonsoft.Json;
using System.Dynamic;
namespace CsvTest
{
class Program
{
publicclass Entity
{
publicint Num { get; set; }
public DateTime Date { get; set; }
publicstring Text { get; set; }
publicbool Flag { get; set; }
public Entity(int num, DateTime date, string text, bool flag)
{
Num = num;
Date = date;
Text = text;
Flag = flag;
}
}
static Entity[] TestData = new Entity[]
{
new Entity(1, new DateTime(2012,12,21), "Normal", true),
new Entity(16, new DateTime(2012,12,21), "Taipei, Taiwan", true),
new Entity(32, new DateTime(2012,12,21), $@"""雙引號""跟,都來一下
換行當然不可少
", false)
};
staticvoid Main(string[] args)
{
//Test1();
//Test2();
Test3();
}
//物件陣列轉成CSV
staticvoid Test1()
{
File.WriteAllText("E:\\CSVLab\\Test1.csv",
CsvSerializer.SerializeToCsv<Entity>(TestData),
//指定new UTF8Encoding(true)產生包含BOM標記的UTF8檔案
//不然Excel直接開啟會有亂碼
new UTF8Encoding(true));
}
staticvoid Test2()
{
var csv = File.ReadAllText("E:\\CSVLab\\Test1.csv");
//記得using ServiceStack啟用擴充方法
var data = csv.FromCsv<List<Entity>>();
Console.WriteLine(JsonConvert.SerializeObject(data, Formatting.Indented));
Console.Read();
}
staticvoid Test3()
{
var csv = File.ReadAllText("E:\\CSVLab\\Test1.csv");
string[] propNames = null;
List<string[]> rows = new List<string[]>();
foreach (var line in CsvReader.ParseLines(csv))
{
string[] strArray = CsvReader.ParseFields(line).ToArray();
if (propNames == null)
propNames = strArray;
else
rows.Add(strArray);
}
Console.WriteLine($"PropNames={string.Join(",", propNames)}");
for (int r = 0; r < rows.Count; r++)
{
var cells = rows[r];
for (int c = 0; c < cells.Length; c++)
{
Console.WriteLine($"[{r},{c}]={cells[c]}");
}
}
Console.Read();
}
}
}
Test1() 順利地輸出 CSV,由結果驗證 CsvSerializer 遇到逗號時會自動加雙引號,遇到內含雙引號也懂得置換。
測試以 Excel 開啟匯出的 CSV,欄位分隔解析完全正確。(注意:匯出中文 CSV 時記得要傳 Encoding.UTF8 或 new UTF8Encoding(true) 參數避免亂碼。參考)
Test2() 將 CSV 還原物件陣列也成功!
Test3() 使用 CsvReader.ParseLine()、CsvReader.ParseFileds() 將 CSV 拆解成多筆資料字串,再逐筆依欄位分解成字串陣列,有自己土砲過的人就知道要判斷 逗號 vs 夾在雙引號中的逗號、換行 vs 夾在雙引號中的換行 有多煩人,有了 ServiceStack.Text,一切簡單多了!
工具箱再添順手兵刃一件!
陸續接獲網友回饋:補充其他處理 CSV 的好選擇:
- CsvHelper mrkt 的程式學習筆記- 使用 CsvHelper - Part.1 資料寫入
- LinqToExcel 讀取 Excel 你還在用 NPOI 嗎-快來試試 LinqToExcel - demo小鋪
- NPOI (但未找到範例)