前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity 基于excel2json批处理读取Excel表并反序列化

Unity 基于excel2json批处理读取Excel表并反序列化

作者头像
汐夜koshio
发布2020-07-28 15:31:29
1.3K0
发布2020-07-28 15:31:29
举报
文章被收录于专栏:U3DU3D

excel2json是一款将Excel表格文件快速生成json和C#数据类的高效插件,详情了解如下:

https://neil3d.github.io/coding/excel2json.html

该插件有两种模式,分别是命令行和图像界面;为了更方便愉快的进行大规模转换,可以写两个批处理文件来实现:

Single文件执行单个选中Excel文件转换,AutoAll则执行该路径下所有xlsx文件;输出文件夹的位置为output,如果该目录下无output文件夹,则自动创建:

Single.bat详情如下:

代码语言:javascript
复制
 1 @SET OUTPUT_FOLDER=.\output
 2 @SET EXE=.\tool\excel2json.exe
 3 
 4 if not exist %OUTPUT_FOLDER% md %OUTPUT_FOLDER%
 5 
 6 @echo off
 7 set path=%1
 8 echo 源文件路径%path%
 9 set name=%~n1
10 echo 源文件名%name%
11 set outputpath=%OUTPUT_FOLDER%\%name%
12 echo 输出文件路径%outputpath%
13 
14 @CALL %EXE% --excel %path% --json %outputpath%.json --header 3 --csharp %outputpath%.cs -a
15 
16 pause

前两行分别为输出目录和.exe文件路径,后面使用该变量作为参数时格式为[%变量名%];[.\]代表相对路径

第四行,如果不存在该路径文件夹则自动创建,注意如果没有这一行也没有对应参数所指示的路径,这时并不会自动创建路径而是会直接报错

第七行,一个%表示参数输入,得到当前选择的首个文件路径

第九行,得到当前选择的首个文件文件名(不包含后缀)

类似的还有:

%~d1\ 得到当前选择的首个文件所在磁盘符

%~dp1 得到当前选择的首个文件目录(不包含文件名和文件后缀名)

%~nx1 得到当前选择的首个文件文件名和后缀

这里主要是为了保持输出文件名与选择文件名一致,最终输出路径为设置的输出路径目录+源文件名

第十四行,调用@CALL 执行对应路径下的exe文件,根据excel2json提供的命令行设置启动参数。

AutoAll.bat详情如下:

代码语言:javascript
复制
 1 @SET EXCEL_FOLDER=.\
 2 @SET OUTPUT_FOLDER=.\output
 3 @SET EXE=.\tool\excel2json.exe
 4 
 5 @ECHO Converting excel files in folder %EXCEL_FOLDER% ...
 6 if not exist %OUTPUT_FOLDER% md %OUTPUT_FOLDER%
 7 
 8 for /f "delims=" %%i in ('dir /b /a-d /s %EXCEL_FOLDER%\*.xlsx') do (
 9     @echo   processing %%~nxi 
10     @CALL %EXE% --excel %EXCEL_FOLDER%\%%~nxi --json %OUTPUT_FOLDER%\%%~ni.json --header 3 --csharp %OUTPUT_FOLDER%\%%~ni.cs -a
11 )
12 pause

上面这个批处理文件在帮助页面中有类似示例,利用for循环对路径内的文件遍历查询和批量执行。具体说明如下:

dir /b /a-d /s 从指定路径遍历搜索文件,路径即为当前文件夹下的所有.xlsx文件,也可以修改前面的excel所在文件夹参数配置其他位置

%%~nxi与%%~ni 与Single.bat中类似,只不过不是1(首个文件)而是循环体中的变量i(当前遍历的文件),i对应数目索引的指定文件

需要注意的是,在cmd模式下的循环变量为单个%+循环标识符(即%i),但在批处理文件中需要两个百分号才行(即%%i)

提供已经写好批处理的文件包下载链接:

https://files.cnblogs.com/files/koshio0219/excel2json.zip

这里统一将Execl导出为了单张数组类型,方便在Unity中进一步反序列化,如果想导出字典,可自行修改,或利用图形界面分别导出

之所以默认导出数组类型,因为Unity默认的JsonUtility解析字典类型几乎是不可能,即使强行可以,那也是用的两个List做对应关系,跟真正的字典类型导出的Json文件格式区别很大,如果直接解析出来就是个空文件。

当然了,如果只是用于数据保存和读写,先序列化后再反序列化回来的话,这样是不会出任何问题的,用两个List来做对应关系来序列化字典是完全可行的,这个可以详细见后面的补充实验;反过来如果专门利用外部文件来执行反序列化,就有很多地方需要额外注意,很容易就反序列化失败。

查看excel2json工程的源代码就可以知道,里边用的Json序列化方式为Newtonsoft.Json,如果实在需要用字典来解析,可以直接导入Newtonsoft.Json到Unity中使用。

为了方便对比,下面分别进行数组Json与字典Json的反序列化测试:

1.数组型Json(或List型)

比如下面这段Json和C#文件:(通过excel2json导出)

代码语言:javascript
复制
 1  [
 2   {
 3     "ID": "4l523",
 4     "Hp": 5,
 5     "Atk": 6.3,
 6     "Def": 7,
 7     "State": ""
 8   },
 9   {
10     "ID": "p6",
11     "Hp": 7,
12     "Atk": 8,
13     "Def": 2.3,
14     "State": ""
15   },
16   {
17     "ID": 0.3,
18     "Hp": 0.2,
19     "Atk": "2.3,7",
20     "Def": 9,
21     "State": ""
22   }
23 ]
代码语言:javascript
复制
1 [System.Serializable]
2 public class Buff
3 {
4     public string ID; // 编号
5     public int Hp; // 血量
6     public float Atk; // 攻击
7     public float Def; // 防御
8     public BuffData State; // 状态
9 }

上面的Json中,因为腹黑的我在Excel表格中故意填错了一些与当前类型不匹配的数据,导致出来的Json中的数据比较怪异,例如第三组中的ID,Hp,Atk,Def与当前的数据类型不符,且Atk一个表格中填了两个数字;

当我们企图直接利用JsonUtility反序列化该数组时,Unity会给你一个错误——JSON must represent an object type.:

1 var data = JsonUtility.FromJson<Buff[]>(json.text);

JsonUtility序列化数组时需要一个额外转换,它必须包含一个默认的签名类型才行,所以在Json文件外围增加一个array的签名即可成功反序列化:

代码语言:javascript
复制
 1 using UnityEngine;
 2 
 3 public class JsonHelper
 4 {
 5     public static T[] GetJsonArray<T>(string json)
 6     {
 7         string newJson = "{ \"array\": " + json + "}";
 8         Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(newJson);
 9         return wrapper.array;
10     }
11 
12     [System.Serializable]
13     private class Wrapper<T>
14     {
15         public T[] array;
16     }
17 }

需要注意的是,如果以该方式反序列化数组,之前导出的Json文件不能包含文件名,在上面的脚本中统一将文件名添加为array。而newJson的文件名称必须与Wrapper类中的泛型数组T[]的变量名保持一致,才能保证反序列化出指定数据。

如果不利用泛型的话,则需要每个文件单独写一个类来进行反序列化,同样的数组的变量名必须与Json中的Array名保持一致。

为了更方便的通过ID来读取数据,也可以将得到的数组再遍历一遍重新写入一个字典中,通过反射在获取ID的值作为键,但前提是规定每一个Json文件中必须有ID这一字段:

代码语言:javascript
复制
 1 public class JsonDatas<T>
 2 {
 3     public Dictionary<string, T> Dict = new Dictionary<string, T>();
 4     public static JsonDatas<T> FromJson(string json)
 5     {
 6         var re = new JsonDatas<T>();
 7         var datas = JsonHelper.GetJsonArray<T>(json);
 8         foreach(var data in datas)
 9         {
10             var info = data.GetType().GetField("ID");
11             var idstr = info.GetValue(data).ToString();
12             re.Dict.Add(idstr, data);
13         }
14         return re;
15     }
16 
17     public T Get(string ID)
18     {
19         return Dict[ID];
20     }
21 }

反射取字段值得时遇到了一个坑,特意记录一下:

Type.GetField(string name) 这个是取字段的值,取不了属性

Type.GetProperty(string name) 这个是取属性的值,取不了字段

这两个取出来的内容是不一样的,请注意区分,不然半天也查不出错误出在哪里(说的就是我本人)

调试后的结果如下,能够成功解析出Json了:

如此顺利甚至让人有点惊讶,之前瞎填的错误数据类型是怎么反序列化的呢?来看看第三组数据为什么没有报错 ,神奇的是,JsonUtility竟然自动帮你转化为了对应的数据类型:

ID 0.3被转为了“0.300000”;Hp 0.2 变为了0;更震惊的是,Atk竟然也没有报错,而是成功解析出了逗号前面的数字,emmm有点迷。

2.字典型Json

如果非要导出字典型Json来反序列化,那就不能再用Unity自带的JsonUtility了,最好导入和序列化时用的是一样的Newtonsoft.Json

这里是与Unity适配的Newtonsoft.Json包JsonNet.9.0.1.unitypackage下载地址:

https://files.cnblogs.com/files/koshio0219/JsonNet.9.0.1.zip

如果是利用Newtonsoft.Json反序列化单个不带任何签名的字典,只用一句话就可以了,不需要建立任何新类:

1 var data = JsonConvert.DeserializeObject<Dictionary<string, Buff>>(json.text);

试比较带签名和不带签名的Json:

代码语言:javascript
复制
 1 {
 2   "Buff": {
 3     "4l523": {
 4       "ID": "4l523",
 5       "Hp": 5,
 6       "Atk": 6.3,
 7       "Def": 7,
 8       "State": ""
 9     },
10     "p6": {
11       "ID": "p6",
12       "Hp": 7,
13       "Atk": 8,
14       "Def": 2.3,
15       "State": ""
16     },
17     "0.3": {
18       "ID": 0.3,
19       "Hp": 2,
20       "Atk": 7,
21       "Def": 9,
22       "State": ""
23     }
24   }
25 }
代码语言:javascript
复制
 1 {
 2   "4l523": {
 3     "ID": "4l523",
 4     "Hp": 5,
 5     "Atk": 6.3,
 6     "Def": 7,
 7     "State": ""
 8   },
 9   "p6": {
10     "ID": "p6",
11     "Hp": 7,
12     "Atk": 8,
13     "Def": 2.3,
14     "State": ""
15   },
16   "0.3": {
17     "ID": 0.3,
18     "Hp": 2,
19     "Atk": 7,
20     "Def": 9,
21     "State": ""
22   }
23 }

只要带有签名或者存在多个表单文件在同一个Json中,就只能重新建立新类并解析该新类了,新类中的变量顺序和变量名都必须与Json文件中的顺序与签名保持一致才能成功反序列化:

代码语言:javascript
复制
1     public class Buffs
2     {
3         //变量名称Buff必须与Json中的签名Buff一样
4         public Dictionary<string, Buff> Buff = new Dictionary<string, Buff>();
5     }

叫人失落的是,Newtonsoft.Json并不会良心的帮你把错误的数据自动转换,而是直接给你抛出一个错误,害的我只能手动修正错误数据,这一点和JsonUtility不同。

补充:

一个有趣的实验——强行用Unity中的字典序列化方式来序列化Json文件会是怎样?

开始之前,我们要明白的是,Unity默认情况下没有给出任何字典序列化的方式,它只能蠢蠢的序列化List或者Array,但这并不能阻止我们,我们可以讨巧的利用ISerializationCallbackReceiver接口来实现一个伪序列化,本质上还是用的两个List:

代码语言:javascript
复制
 1 using UnityEngine;
 2 using System;
 3 using System.Collections.Generic;
 4 
 5 // Dictionary<TKey, TValue>
 6 [Serializable]
 7 public class Serialization<TKey, TValue> : ISerializationCallbackReceiver
 8 {
 9     [SerializeField]
10     List<TKey> keys;
11     [SerializeField]
12     List<TValue> values;
13 
14     Dictionary<TKey, TValue> target;
15     public Dictionary<TKey, TValue> ToDictionary() { return target; }
16 
17     public Serialization(Dictionary<TKey, TValue> target)
18     {
19         this.target = target;
20     }
21 
22     public void OnBeforeSerialize()
23     {
24         keys = new List<TKey>(target.Keys);
25         values = new List<TValue>(target.Values);
26     }
27 
28     public void OnAfterDeserialize()
29     {
30         var count = Math.Min(keys.Count, values.Count);
31         target = new Dictionary<TKey, TValue>(count);
32         for (var i = 0; i < count; ++i)
33         {
34             target.Add(keys[i], values[i]);
35         }
36     }
37 }

把之前反序列化出的数据再用该伪序列化方式来序列化为Json文件:

代码语言:javascript
复制
1         var SerializedBuff= new Serialization<string, Buff>(new Dictionary<string, Buff>());
2         var data = JsonConvert.DeserializeObject<Buffs>(json.text);
3         foreach(var item in data.Buff)
4         {
5             SerializedBuff.ToDictionary().Add(item.Key, item.Value);
6         }
7         var jsont = JsonUtility.ToJson(SerializedBuff);
8         Debug.Log(jsont);

jsont文本内容如下:

代码语言:javascript
复制
 1 {
 2     "keys":[
 3         "4l523",
 4         "p6",
 5         "0.3"],
 6     "values":[
 7         {
 8             "ID":"4l523",
 9             "Hp":5,
10             "Atk":6.300000190734863,
11             "Def":7.0,
12             "State":{
13             }
14         },
15         {
16             "ID":"p6",
17             "Hp":7,
18             "Atk":8.0,
19             "Def":2.299999952316284,
20             "State":{
21             }
22         },
23         {
24             "ID":"0.3",
25             "Hp":2,
26             "Atk":7.0,
27             "Def":9.0,
28             "State":{
29             }
30         }]
31 }

我们发现它确实和之前的Json文件结构大不相同,因为它根本不是字典而是两个List做对应关系,实际上是Keys在一起Values在一起,只不过它们的索引是相互对应的。

当我们已经将Json文件转化为了Unity可识别的形式后,就可以很容易的再进行反序列化了:

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-07-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档