Excel高效转Json工具类详解

文章摘要

该工具类提供Excel到Json的转换功能,主要包含:

ExcelToJsonToolEditor:核心编辑器类,处理Excel批量/单表转换、文件路径操作
类型定义:TypeValue存储字段信息,JsonKeyType标识键类型
转换流程:读取Excel→生成Json→自动创建对应C#配置类代码
扩展性:支持指定表转换、错误处理、代码模板化生成
关键方法:DoXlsxToJson处理单表转换,GenerateSheetCsharpCode动态生成配置类代码。适用于Unity项目配置表自动化处理,需配合Newtonsoft.Json库使用。

下面是整理后的Excel转Json工具类,结构更清晰,注释更详细,便于维护和扩展。
主要分为:

  • Editor工具主类(ExcelToJsonToolEditor)
  • 类型辅助(TypeValue、JsonKeyType)
  • 工具方法(EditorUtil)
  • 代码生成相关(UICodeConfig)

你可以根据实际项目需求进一步扩展类型、增加校验、优化代码生成等。


#if UNITY_EDITOR
using Excel;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using UnityEditor;
using UnityEngine;

/// 
/// Excel转Json工具的Editor扩展
/// 
[CustomEditor(typeof(ExcelToJsonTool))]
public class ExcelToJsonToolEditor : Editor
{
    private string _curFinishText = "";
    private string _excelName = "";
    private string _csharpText = "";

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        _excelName = EditorGUILayout.TextField("Excel名(不填全转):", _excelName);

        if (GUILayout.Button("Excel表转Json"))
        {
            _curFinishText = "";
            DoAllXlsxToJson(_excelName);
            _curFinishText += "转表结束...";
        }
        else if (GUILayout.Button("打开Excel文件夹"))
        {
            string path = System.Environment.CurrentDirectory + "/Excel/init.txt";
            EditorUtility.RevealInFinder(path);
        }
        else if (GUILayout.Button("打开Json文件夹"))
        {
            string path = System.Environment.CurrentDirectory + "/Assets/Resources/Config/init.txt";
            EditorUtility.RevealInFinder(path);
        }

        EditorGUILayout.BeginHorizontal();
        GUILayout.Label(_curFinishText);
        EditorGUILayout.EndHorizontal();
    }

    /// 
    /// 批量或单个Excel转Json
    /// 
    void DoAllXlsxToJson(string singleExcel = "")
    {
        string filepath = Application.dataPath + "/Script/Tools/JsonClass.cs";
        _csharpText = File.ReadAllText(filepath);
        ExcelToJsonTool excels = target as ExcelToJsonTool;
        foreach (ExcelConfig excelName in excels._Excels)
        {
            if (excelName.name.Equals(singleExcel) || string.IsNullOrEmpty(singleExcel))
            {
                _curFinishText += "读取Excel:" + excelName.name + "\n";
                DoXlsxToJson(excelName);
                _curFinishText += "\n";
                if (!string.IsNullOrEmpty(singleExcel))
                    break;
            }
        }

        // 写回C#代码
        if (File.Exists(filepath))
        {
            File.SetAttributes(filepath, FileAttributes.Normal);
            File.WriteAllText(filepath, _csharpText);
        }
    }

    /// 
    /// 单个Excel转Json
    /// 
    private bool DoXlsxToJson(ExcelConfig config)
    {
        string dataPath = System.Environment.CurrentDirectory;
        string xlsxPath = dataPath + "/Excel/" + config.name + ".xlsx";
        string savePath = Application.dataPath + "/Resources/Config/";
        FileStream stream = null;
        try
        {
            stream = File.Open(xlsxPath, FileMode.Open, FileAccess.Read);
        }
        catch (IOException)
        {
            _curFinishText = "请关闭Excel后再进行";
            if (stream != null) stream.Close();
            return false;
        }

        if (stream == null)
            return false;

        IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
        excelReader.Read(); // 必须先Read一次
        DataSet result = excelReader.AsDataSet();
        if (result == null)
        {
            Debug.LogError(config.name + " Has Empty Line!");
            return false;
        }

        foreach (string exSheet in config.sheets)
        {
            List<TypeValue> kvList = new List<TypeValue>();
            JsonKeyType idType = ReadSingleSheet(result.Tables[exSheet], savePath + exSheet + ".json", ref kvList);
            GenerateSheetCSharpCode(exSheet, kvList, idType);
            _curFinishText += $"----转表:{exSheet}.json完成\n";
        }

        stream.Close();
        return true;
    }

    /// 
    /// 生成C#代码
    /// 
    private void GenerateSheetCSharpCode(string sheetName, List<TypeValue> memberList, JsonKeyType keyType)
    {
        int startIndex = _csharpText.IndexOf("ConfigDefine");
        int endIndex = _csharpText.IndexOf("}", startIndex) + 1;
        string defineString = _csharpText.Substring(startIndex, endIndex - startIndex);
        string strHas = sheetName + " = ";
        if (defineString.Contains(strHas))
            return; // 已有定义

        string classDefine = EditorUtil.format(UICodeConfig.classDefine, sheetName, sheetName);
        _csharpText = _csharpText.Insert(endIndex - 1, classDefine);

        // 类定义
        startIndex = _csharpText.IndexOf("ConfigDefine");
        endIndex = _csharpText.IndexOf("}", startIndex) + 1;

        string configStr = sheetName + "Config";
        string inhiertStr = $"Config<{configStr}>";
        string className = $"\npublic class {configStr} : {inhiertStr}";
        List<string> memberStrList = new List<string>();
        foreach (var member in memberList)
        {
            string memberStr = EditorUtil.format(UICodeConfig.variable, member.memberType, member.memberName);
            memberStrList.Add(memberStr);
        }
        string finalMemberStr = string.Join("\n", memberStrList);
        className += $"\n{{\n{finalMemberStr}";

        // 构造函数
        string keyTypeStr = keyType == JsonKeyType.INT ? "JsonKeyType.INT" : "JsonKeyType.STRING";
        string constructStr = EditorUtil.format(UICodeConfig.construct, configStr, sheetName, keyTypeStr) + " { }";
        className += $"\n\t{constructStr}\n}}\n";

        _csharpText = _csharpText.Insert(endIndex + 1, className);
    }

    /// 
    /// 读取单个Sheet并导出Json
    /// 
    private JsonKeyType ReadSingleSheet(DataTable dataTable, string jsonPath, ref List<TypeValue> kvList)
    {
        JsonKeyType type = JsonKeyType.INT;
        kvList = new List<TypeValue>();
        int rows = dataTable.Rows.Count;
        int columns = dataTable.Columns.Count;
        DataRowCollection collect = dataTable.Rows;

        // 字段名和类型
        string[] jsonFields = new string[columns];
        string[] jclassTypes = new string[columns];
        for (int i = 0; i < columns; i++)
        {
            jsonFields[i] = collect[1][i].ToString();
            jclassTypes[i] = collect[2][i].ToString();

            string memberName = jsonFields[i];
            if (memberName.Equals("ID") || memberName.Equals("IDs"))
                continue;

            TypeValue kv = new TypeValue
            {
                memberName = jsonFields[i],
                memberType = GetCSharpType(jclassTypes[i])
            };
            kvList.Add(kv);
        }

        // 数据行
        List<object> objsToSave = new List<object>();
        for (int i = 3; i < rows; i++)
        {
            JObject postedJObject = new JObject();
            bool isEnd = false;
            for (int j = 0; j < columns; j++)
            {
                string memberName = jsonFields[j];
                string classType = jclassTypes[j];
                string str = collect[i][j].ToString();
                object value = ParseCellValue(classType, str);

                if (memberName.Equals("IDs"))
                    type = JsonKeyType.STRING;
                if (memberName.Equals("ID") || memberName.Equals("IDs"))
                {
                    if (string.IsNullOrEmpty(str))
                    {
                        isEnd = true;
                        break;
                    }
                }
                postedJObject.Add(memberName, JToken.FromObject(value));
            }
            if (isEnd)
                break;
            objsToSave.Add(postedJObject);
        }

        // 保存为Json
        string content = JsonConvert.SerializeObject(objsToSave, Formatting.Indented);
        SaveFile(content, jsonPath);
        return type;
    }

    /// 
    /// 类型映射
    /// 
    private string GetCSharpType(string excelType)
    {
        switch (excelType)
        {
            case "String": return "string";
            case "Int": return "int";
            case "ArrayInt":
            case "ArrayIntLine": return "int[]";
            case "ArrayString":
            case "StringArrayLine": return "string[]";
            case "Float": return "float";
            case "ArrayFloat": return "float[]";
            default: return "string";
        }
    }

    /// 
    /// 单元格值解析
    /// 
    private object ParseCellValue(string classType, string str)
    {
        switch (classType)
        {
            case "String":
                return str;
            case "Int":
                return EditorUtil.ParseInt(str);
            case "ArrayInt":
                return string.IsNullOrEmpty(str) ? new int[0] : Array.ConvertAll(str.Split(','), EditorUtil.ParseInt);
            case "ArrayIntLine":
                return string.IsNullOrEmpty(str) ? new int[0] : Array.ConvertAll(str.Split('|'), EditorUtil.ParseInt);
            case "ArrayString":
                return string.IsNullOrEmpty(str) ? new string[0] : str.Split(',');
            case "StringArrayLine":
                return string.IsNullOrEmpty(str) ? new string[0] : str.Split('|');
            case "Float":
                return EditorUtil.ParseFloat(str);
            case "ArrayFloat":
                return string.IsNullOrEmpty(str) ? new float[0] : Array.ConvertAll(str.Split(','), EditorUtil.ParseFloat);
            default:
                return str;
        }
    }

    /// 
    /// 保存文件
    /// 
    private void SaveFile(string content, string jsonPath)
    {
        if (File.Exists(jsonPath))
            File.Delete(jsonPath);
        using (var streamWriter = new StreamWriter(new FileStream(jsonPath, FileMode.Create)))
        {
            streamWriter.Write(content);
        }
    }

    // 类型定义
    public class TypeValue
    {
        public string memberType;
        public string memberName;
    }

    public enum JsonKeyType
    {
        INT,
        STRING
    }

    public class UICodeConfig
    {
        public const string classDefine = "\tpublic static string {0} = \"{1}\";\n";
        public const string variable = "\tpublic {0} {1};";
        public const string construct = "public {0}() : base(ConfigDefine.{1},{2})";
    }
}

/// 
/// 编辑器工具方法
/// 
public static class EditorUtil
{
    public static int ParseInt(string data)
    {
        if (string.IsNullOrEmpty(data))
            return 0;
        int val;
        return int.TryParse(data, out val) ? val : 0;
    }

    public static float ParseFloat(string data)
    {
        if (string.IsNullOrEmpty(data))
            return 0f;
        float val;
        return float.TryParse(data, out val) ? val : 0f;
    }

    public static string format(string valuestr, params object[] paramStrs)
    {
        try
        {
            return string.Format(valuestr, paramStrs);
        }
        catch (Exception)
        {
#if UNITY_EDITOR
            Debug.LogError(string.Format(": {0} 参数数量不匹配", valuestr));
#endif
            return valuestr;
        }
    }
}
#endif

说明

  • 类型扩展:如需支持更多类型(如bool、enum、struct等),只需在GetCSharpTypeParseCellValue中扩展即可。
  • 代码生成:自动插入ConfigDefine和数据类定义,便于后续类型安全读取。
  • 健壮性:增加了空值判断和using语法,防止资源泄露。
  • 结构清晰:每个功能块有独立方法,便于维护和扩展。

你可能感兴趣的:(游戏工具链,excel,json)