Attribute + TypeConverter 實現 Excel To Json
起因
最近在專案上需要實現一個功能,把 Excel 的內容轉成 Json 作為配置檔案,對於 Excel 的操作,有個開源的類庫 ofollow,noindex" target="_blank">Epplus ,而對於 Json 序列化,用到了 Newtonsoft 的 Json.NET ,實現上使用了 Attribute + TypeConverter,Attribute 用於標記列與屬性的對應關係,TypeConverter 用於處理特殊資料結構。
先看下效果:
Excel 資料

轉換後Json

可以看到 C 列 含有空格,轉換後變成了下劃線,D 列被轉換成了 List<string>
呼叫:
static void Main(string[] args) { using (var fileStream = File.Open("../../test.xlsx", FileMode.Open, FileAccess.Read)) { xlPackage = new ExcelPackage(fileStream); var workBook = xlPackage.Workbook; var workSheet = workBook.Worksheets["sheet1"]; var converter = new ModelConverter<TestModel>(workSheet, 3); var result = converter.Convert(); var json = JsonConvert.SerializeObject(result); } }
是不是很簡潔呀
Epplus
基本介紹
Epplus 是用 .net 實現的用於對 Excel 進行讀寫操作的開源類庫,封裝了對 Excel 表格的讀寫操作,功能強大,還能生成公式。由於我們不負責維護 Excel ,只是從裡面讀取內容並轉換成 Json,因此只需要用到 Epplus 提供的讀取 Excel 的功能就夠用了。Epplus 提供瞭如下功能:
- 單元格範圍讀取
- 單元格樣式(邊框,字型顏色,填充顏色,字型,數字格式,對齊樣式)
- 資料驗證
- 帶條件格式化
- 圖表
- 插入圖片
- 插入形狀
- Comments
- 表格
- 資料透視表
- 檔案保護
- 加密
- VBA 指令碼
- 公式計算等
安裝
直接使用 Nuget 命令安裝引用
Install-Package EPPlus -Version 4.5.2.1
注意
Epplus 基於 GNU License,如果直接修改和使用原始碼,由於 GNU License 的傳染性,使得你的專案必須以相同的 License 進行授權,即必須開放原始碼,所以使用原始碼要慎重,最好通過 Nuget 命令使用編譯好的類庫檔案(dll),而不要直接使用原始碼
Json.NET
Json.NET 是 Newtonsoft 提供的一個強大的處理 Json 文字的 .net 類庫,實現了 Json 序列化,反序列化,按照 Json 路徑訪問,XML Json 互轉等功能,這裡我們只用到了 Json 序列化。
安裝
直接使用 Nuget 命令安裝
Install-Package Newtonsoft.Json -Version 11.0.2
實現思路
要實現 Excel to Json,大體分為四個步驟:
- 找到 Excel 表中每一列與 object 屬性的對應關係
- 遍歷 Excel 表中的每一行,轉換成 object 集合
- 對於單元格中的資料,可能需要做特殊處理,比如讀取單元格並替換內容裡的空格,把單元格內容按一定格式轉換成列表等
- 把生成的 object 集合轉換成 J
本著方便擴充套件、解耦的原則,想到了一個 Attribute + TypeConverter 的實現,利用 Attribute 標記屬性與 Excel 表格每列的對應關係,方便統一集中管理。用 TypeConvertor 能夠使用 .net 自帶的 TypeConverter Attribute 對需要特殊處理的欄位進行自動格式轉換。
Attribute
關於 Attribute 的相關知識請參考 C#系列之Attribute與反射
TypeConverter
TypeConverter 是 .net 提供的用於型別轉換的基類,通過 override CanConvertFrom CanConvertTo ConvertFrom ConvertTo 方法來實現從特定型別轉換到該型別,或者通過該型別轉換成特定型別,在這裡因為我們不需要把屬性型別轉換成其他型別,只需要把從 Excel 裡面來的資料,通常是 string,轉換成 object 屬性宣告的型別即可,因此只需要實現 CanConvertFrom 和 ConvertFrom 。另外它需要一個 TypeConverterAttribute 配合一起使用,標記當前屬性使用什麼樣的 TypeConverter 可以轉換成該屬性的宣告型別,比如:
public class TestModel { ... [WorkSheetColumn("D")] [TypeConverter(typeof(ListStringConverter))] public List <string> Hobby { get; set; } ... } public class ListStringConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } return base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { var strValue = value.ToString(); if (string.IsNullOrEmpty(strValue) || strValue == "N/A") { return new List <string> (); } return value.ToString().Split(',').ToList(); } return value; } } </string> </string>
標記對應關係
為了標記 object 屬性和 Excel 單元格的對應關係,我們需要實現一個 WorksheetColumnAttribute , 它只包含一個屬性 ColumnName , 限定用於 Property,為什麼限定為 Property,下面說
[AttributeUsage(AttributeTargets.Property)] public class WorkSheetColumnAttribute : Attribute { public string ColumnName { get; private set; } public WorkSheetColumnAttribute(string columnName) { ColumnName = columnName; } }
Model 實現
Model 儲存了所有的對應關係及轉換關係,方便集中管理
public class TestModel { [WorkSheetColumn("A")] public string Name { get; set; } [WorkSheetColumn("B")] public int Age { get; set; } [WorkSheetColumn("C")] [TypeConverter(typeof(NoSpaceConverter))] public string FavoriteFruit { get; set; } [WorkSheetColumn("D")] [TypeConverter(typeof(ListStringConverter))] public List <string> Hobby { get; set; } } </string>
轉化
轉化分為2步
- 把 Model 上所有的 WorkSheetColumn 和 TypeConverter Attribute 找出,建立 列名-屬性-TypeConverter 的對應關係,定義了一個叫做 MappingInfo 的類來儲存對應關係。由於每一行的資料結構都一致,所以只需要建立一次對應關係就可以了。上面提到 WorkSheetColumnAttribute 限定只能應用到 Property,是因為我們在使用反射的時候直接遍歷 Model 上的所有 Property,並儲存成 PropertyInfo,這樣就不用考慮 field 的情況,能夠簡化實現
- 迴圈遍歷 Excel 的所有行,呼叫對應關係進行轉換
public class ModelConverter<T>where T : class, new() { private readonly ExcelRange _excelRange; private readonly int _startRow; private readonly int _endRow; public ModelConverter(ExcelWorksheet workSheet, int startRow) { _excelRange = workSheet.Cells; _startRow = startRow; _endRow = workSheet.Dimension.End.Row; } public IList <t> Convert() { var mappingDic = GetMappingDic(); var result = new List<T>(); for (var index = _startRow; index <= _endRow; index++) { var instance = new T(); foreach (var mappingInfo in mappingDic) { mappingInfo.PropertyInfo.SetValue(instance, mappingInfo.TypeConverter.ConvertFrom(_excelRange[string.Format("{0}{1}", mappingInfo.ColumnName, index)].Text), (object[])null); } result.Add(instance); } return result; } private List<MappingInfo> GetMappingDic() { var properties = typeof(T).GetProperties(); var result = new List<MappingInfo>(); foreach (var propertyInfo in properties) { var workColumnAttribute =(WorkSheetColumnAttribute)propertyInfo.GetCustomAttributes(typeof(WorkSheetColumnAttribute), false).FirstOrDefault(); if (workColumnAttribute == null) { continue; } var mappingInfo =new MappingInfo() { ColumnName = workColumnAttribute.ColumnName, PropertyInfo = propertyInfo, }; var propertyDescriptorCollection = TypeDescriptor.GetProperties(typeof(T)); mappingInfo.TypeConverter = propertyDescriptorCollection.Find(propertyInfo.Name, false).Converter; result.Add(mappingInfo); } return result; } public class MappingInfo { public PropertyInfo PropertyInfo { get; set; } public string ColumnName { get; set; } public TypeConverter TypeConverter { get; set; } } } </t>
Json 序列化
序列化用到 Json.NET 的 JsonConvert 類,一行程式碼搞定
var json = JsonConvert.SerializeObject(result);
優缺點
博主為你專屬推薦
優點:
- 把屬性與列名對應關係集中在一起,方便維護,並且易於擴充套件,加入新的列只需要新加屬性就可以了
- 使用 TypeConveterAttribute 進行特殊處理,對於新的型別處理只需新加 TypeConverter 就可以,不用修改 ModelConverter 類,做到了開閉有度
- 只建立一次對應關係
- 用這種思路可以實現其他 class 到 class 的型別轉換
缺點:
- 只實現了簡單的 Excel 列對應關係,對於複雜的表格,還需要進一步考慮對應關係的設計,如好幾列共同構成一個子型別
完整程式碼
見 Github
參考連結
有問題請在評論區留言