最近在项目上需要实现一个功能,把 Excel 的内容转成 Json 作为配置文件,对于 Excel 的操作,有个开源的类库 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 是用 .net 实现的用于对 Excel 进行读写操作的开源类库,封装了对 Excel 表格的读写操作,功能强大,还能生成公式。由于我们不负责维护 Excel ,只是从里面读取内容并转换成 Json,因此只需要用到 Epplus 提供的读取 Excel 的功能就够用了。Epplus 提供了如下功能:
安装
直接使用 Nuget 命令安装引用
- Install-Package EPPlus -Version 4.5.2.1
注意
Epplus 基于 GNU License,如果直接修改和使用源码,由于 GNU License 的传染性,使得你的项目必须以相同的 License 进行授权,即必须开放源代码,所以使用源码要慎重,最好通过 Nuget 命令使用编译好的类库文件(dll),而不要直接使用源代码
Json.NET 是 Newtonsoft 提供的一个强大的处理 Json 文本的 .net 类库,实现了 Json 序列化,反序列化,按照 Json 路径访问,XML Json 互转等功能,这里我们只用到了 Json 序列化。
安装
直接使用 Nuget 命令安装
- Install-Package Newtonsoft.Json -Version 11.0.2
要实现 Excel to Json,大体分为四个步骤:
本着方便扩展、解耦的原则,想到了一个 Attribute + TypeConverter 的实现,利用 Attribute 标记属性与 Excel 表格每列的对应关系,方便统一集中管理。用 TypeConvertor 能够使用 .net 自带的 TypeConverter Attribute 对需要特殊处理的字段进行自动格式转换。
关于 Attribute 的相关知识请参考 C#系列之Attribute与反射
TypeConverter 是 .net 提供的用于类型转换的基类,通过 override CanConvertFrom CanConvertToConvertFrom ConvertTo 方法来实现从特定类型转换到该类型,或者通过该类型转换成特定类型,在这里因为我们不需要把属性类型转换成其他类型,只需要把从 Excel 里面来的数据,通常是 string,转换成 object 属性声明的类型即可,因此只需要实现 CanConvertFrom 和 ConvertFrom。另外它需要一个 TypeConverterAttribute 配合一起使用,标记当前属性使用什么样的 TypeConverter 可以转换成该属性的声明类型,比如:
- public class TestModel
- {
- ...
- [WorkSheetColumn("D")]
- [TypeConverter(typeof(ListStringConverter))]
- public List 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();
- }
-
- return value.ToString().Split(',').ToList();
- }
-
- return value;
- }
- }
为了标记 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 保存了所有的对应关系及转换关系,方便集中管理
- 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 Hobby { get; set; }
- }
转化分为2步
- 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 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; }
- }
- }
序列化用到 Json.NET 的 JsonConvert 类,一行代码搞定
- var json = JsonConvert.SerializeObject(result);
优点:
缺点:
见 Github