[2023-12-18 更新] 改推薦使用 CsvHelper

做專案免不了遇到匯出或讀取 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
    {
        public class Entity
        {
            public int Num { get; set; }
            public DateTime Date { get; set; }
            public string Text { get; set; }
            public bool 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)
        };
 
 
        static void Main(string[] args)
        {
            //Test1();
            //Test2();
            Test3();
        }
 
        //物件陣列轉成CSV
        static void Test1()
        {
            File.WriteAllText("E:\\CSVLab\\Test1.csv", 
                CsvSerializer.SerializeToCsv<Entity>(TestData),
                //指定new UTF8Encoding(true)產生包含BOM標記的UTF8檔案
                //不然Excel直接開啟會有亂碼
                new UTF8Encoding(true));
        }
 
        static void 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();
        }
 
        static void 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 的好選擇:


Comments

Be the first to post a comment

Post a comment