源起

由于游戏策划异常偏爱用excel编写数据。很多数据可以用二维表的形式处理。但是还有一些数据更方便用树形结构存储。如果同时能也写在excel中,就可以方便双方的协作。
之前在网络上找到过一些xlsx还原为json的代码,但是大都为了各自的需求而设计。且很难表示出复杂的属性结构。比如无穷深度的object和array层叠互套。为此,我制定了一个规则,可以实现上面的需求。
这里描述流程使用CocosCreator 2.6.6。但实际转换核心与引擎无关。

基础

xlsx首先经过预处理,导出为原始json。这个过程使用npm上面的 node-xlsx简单完成。我只处理之后的解析操作。游戏中使用自己的方式读这个json即可。具体我写成了一个packages,在creator工具栏里进行这一步的导原始json。
数据表如下:(测试各种转换情况,没有实际意义,因此看起来有点夸张)

规则说明

  1. 左格是右格的键
  2. 右格是左格的值
  3. 键格以点结尾,值为对象(非数组),键自动去除点
  4. 键以减号结尾,值为数组,键自动去除减号
  5. 左格为空,相当于沿用上面的键
  6. 数组对应的右格只有值
  7. 数组中嵌套子对象或者数组同样使用点或减号并忽略到具体键值 【如27,29行】
  8. 相同的键会被合并【如2,18行】【7,13,16,17行】
  9. 空的一整行会被忽略

node-xlsx的使用

let xlsx = require('node-xlsx');//... key是文件名
let srcFile = path.normalize(`${__dirname}/../../GameDataDesign/${key}.xlsx`);
let tarFile = path.normalize(`${__dirname}/../../assets/${key}.json`);
Editor.log('开始导入数据', srcFile, '->', tarFile);const workSheetsFromFile = xlsx.parse(srcFile);
let book = workSheetsFromFile.filter(_ => !_.name.startsWith('__'));
let jsonText = JSON.stringify(book); 阻止导出注释:首行双下划线“__”所在的列,和行中格以双斜线“//”开头的后面的格(包含)
if (['Sheet1'].includes(key)) { // 只针对特定的表应用此特性,可以按需要进行修改for (let i = 0; i < book.length; i++) {let sheet = book[i];let firstRow = sheet.data[0];let keepFlag = firstRow.map(_ => {if (typeof (_) == 'string') {return !_.startsWith('__');}return true;});let data = [];for (let j = 0; j < sheet.data.length; j++) {let row = sheet.data[j];if (row != null && row.length > 0 && typeof (row[0]) == 'string' && row[0].startsWith(`//`)) continue;let leanRow = [];for (let k = 0; k < row.length; k++) {let value = row[k];if (typeof (value) == 'string' && value.startsWith(`//`)) break;if (keepFlag[k] != null && !keepFlag[k]) {} else {leanRow.push(value);}}// sheet.data[j] = leanRow;data.push(leanRow);}sheet.data = data;}
}
Editor.log('jsonText', jsonText);let str = Editor.assetdb.exists(`db://assets/${key}.json`);
if (str) {Editor.assetdb.saveExists(`db://assets/${key}.json`, jsonText, function (err, results) { });
} else {Editor.assetdb.create(`db://assets/${key}.json`, jsonText, function (err, results) { });
}

这里只用这其中的一个vars表做说明,node-xlsx处理之后的结果JSON形式:

[{"name": "vars","data": [["version", 1],["daily.", "refreshAt", 0],["season.", "refreshAt", 0.08333333333333333],["achievement.", "key1", "a"],[null, "key1b", "b"],[null, "key2", 100],[null, "key3-", 1],[null, null, 2],[null, null, 3],[],[null, null, 4],[null, null, 5],[null, "key3-", 6],[null, "key.", "c", 5],[null, null, "d", 6],[null, "key3-", 7],[null, "key3-", 8],["daily.", 10, "a"],[null, 20, "b"],[null, "key2", true],[null, "key3", 44548.73535729167],[null, "key4.", "a", 1],[null, null, "b", 2],[null, null, "c-", 10],[null, null, null, 20],[null, null, null, 30],[null, null, null, "a.", "key", "value"],[null, null, null, null, "key2", "value2"],[null, null, null, "b-", 1],[null, null, null, null, 2],[null, null, null, null, 3]]},
//... 其它的数据表
]

转换函数,单文件

下面这个ts文件就可以把代码拷走使用。

// JsonFromNodeXlsxTools.ts
// write by Ethan @2021.12.18export function jsonObjectTree(data: any[][], r = 0, l = 0) {let output = {};makeObj(0, 0, output, data);return output;
}export function jsonArrayTree(data: any[][], r = 0, l = 0) {let output = [];makeArray(0, 0, output, data);return output;
}function isDirectArrayKey(value) {if (typeof (value) == 'string') {let v = value.trim();if (v.startsWith('[') && v.endsWith(']')) {return true;}}return false;
}function isArrayKey(value) {if (typeof (value) == 'string') {if (value.endsWith('-')) return true;}return false;
}function isObjectKey(value) {if (typeof (value) == 'string') {if (value.endsWith('.')) return true;}return false;
}function isDate(key) {if (typeof (key) == 'string') {if (key.endsWith('<Date>')) return true;}return false;
}// 要确保输入的key是需要leankey操作的再使用该函数
function leanKey(key: string) {if (key.endsWith('<Date>')) return key.substring(0, key.length - 6);return key.substring(0, key.length - 1);
}function parseDate(raw: number): Date {let oneDay = 86400000;let msOfTheDay = raw * oneDay;let date = new Date(msOfTheDay);date.setUTCFullYear(date.getUTCFullYear() - 70);date.setUTCDate(date.getUTCDate() - 2);return date;
}function parseDirectArray(raw: string): any[] {if (isDirectArrayKey(raw)) {let trimed = raw.trim();let content = trimed.substring(1, trimed.length - 1);let item = content.split(',');let result: any[] = item.map(_ => {if (isNaN(Number(_))) {return _;}return Number(_);});return result;}
}function makeObj(r: number, l: number, obj: {}, data: any[][]) {for (let pr = r; pr < data.length; pr++) {let row = data[pr];// console.log('jfn', r, pr, row);// 判断已经结束了,递归末端let end = false;if (pr > r) {for (let _l = l - 1; _l >= 0; _l--) {let _v = row[_l];if (pr == r && _l == l - 1) continue;if (_v != null) end = true;}}if (end) break;// 内层递归用到的行,在本层是空行,略过let value = row[l];if (value == null) continue;if (isObjectKey(value)) {value = leanKey(value);if (value == 'w') {this.log('value', value);}obj[value] ?? (obj[value] = {});makeObj(pr, l + 1, obj[value], data);} else if (isArrayKey(value)) {value = leanKey(value);obj[value] ?? (obj[value] = []);makeArray(pr, l + 1, obj[value], data);} else if (isDirectArrayKey(value2)) {obj[value] = parseDirectArray(value2);} else if (isDate(value)) {value = leanKey(value);let vov = row[l + 1];let date = parseDate(vov);obj[value] = date;} else {let vov = row[l + 1];obj[value] = vov;}}
};function makeArray(r: number, l: number, obj: any[], data: any[][]) {for (let pr = r; pr < data.length; pr++) {let row = data[pr];// 判断已经结束了,递归末端let end = false;if (pr > r) {for (let _l = l - 1; _l >= 0; _l--) {let _v = row[_l];if (pr == r && _l == l - 1) continue;if (_v != null) end = true;}}if (end) break;// 内层递归用到的行,在本层是空行,略过let value = row[l];if (value == null) continue;if (isObjectKey(value)) {let _obj = {};obj.push(_obj);makeObj(pr, l + 1, _obj, data);} else if (isArrayKey(value)) {let array = [];obj.push(array);makeArray(pr, l + 1, array, data);} else if (isDirectArrayKey(value2)) {obj[value] = parseDirectArray(value2);} else {obj.push(value)}}
}

调用方法和结果

import { jsonArrayTree, jsonObjectTree } from "../Basic/JsonFromNodeXlsxTools";
// ... sheets对应整个工作簿,此时只处理vars表
sheets.forEach(sheet => {if (sheet.name == 'vars') {output[sheet.name] = jsonObjectTree(sheet.data);}
});

这个是结果:

{"version": 1,"daily": {"10": "a","20": "b","refreshAt": 0,"key2": true,"key3": 44548.73535729167,"key4": {"a": 1,"b": 2,"c": [10, 20, 30, { "key": "value", "key2": "value2" }, [1, 2, 3]]}},"season": { "refreshAt": 0.08333333333333333 },"achievement": {"key1": "a","key1b": "b","key2": 100,"key3": [1, 2, 3, 4, 5, 6, 7, 8],"key": { "c": 5, "d": 6 }}
}

问题修正

fixed: 对象第一个键也对象的则内部对象丢失

简易数组行

在之前,数组在xlsx文件中需要纵向扩展为多行。但在很多情况下,数组都是末端节点,构成很简单,比如并列的一排数字,字符串等等,如果还需要写多行,那么可读性较差。为了克服这个问题,增加了支持简易数组行的特性。

之前,想要一个数组,那么一定在key单元格后面写个’-'减号。而现在,如果后面接简易数组行,就直接在key后面写这个中括号的数组即可。另外,如果是字符串数组,则省略引号。如上图的[ta2,ta3]
特性已经改入上面贴出的代码,关键词:DirectArray。

Xlsx转Json(JS Object/Array) Javascript/Typescript版本相关推荐

  1. php json to object,PHP JSON_FORCE_OBJECT函数实现强转对象

    JSON_FORCE_OBJECT 在多级数组中,JSON_FORCE_OBJECT会将所有嵌套数值数组编码为对象. 如果你只关注第一级数组(例如,使其适合作为MySQL JSON列),那么可以将第一 ...

  2. 在JavaScript中使用json.js:Ajax项目之POST请求(异步)

    经常在百度搜索框输入一部分关键词后,弹出候选关键热词.现在我们就用Ajax技术来实现这一功能. 一.下载json.js文件 百度搜一下,最好到json官网下载,安全起见. 并与新建的两个文件部署如图 ...

  3. 再谈js对象数据结构底层实现原理-object array map set

    2019独角兽企业重金招聘Python工程师标准>>> 如果有java基础的同学,可以回顾下<再谈Java数据结构-分析底层实现与应用注意事项>:java把内存分两种:一 ...

  4. js object转json

    jQuery.extend( { /** * @see 将json字符串转换为对象 * @param json字符串 * @return 返回object,array,string等对象 */ eva ...

  5. (The application/json Media Type for JavaScript Object Notation (JSON))RFC4627-JSON格式定义

    原文  http://laichendong.com/rfc4627-zh_cn/ 摘要 JavaScript Object Notation (JSON)是一个轻量级的,基于文本的,跨语言的数据交换 ...

  6. vue纯前端实现json导出为excel文件(xlsx.full.min.js)

    废话不多说,能实现导出为excel的库也不少,这次我用的是xlsx.full.min.js(git地址:https://github.com/SheetJS/sheetjs) 在utils里简单封装一 ...

  7. 在JavaScript中使用json.js:访问JSON编码的某个值

    演示: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3. ...

  8. [ Typescript 手册] JavaScript `Array` 在 Typescript 中的接口

    [ Typescript 手册] JavaScript `Array` 在 Typescript 中的接口 本文地址:https://blog.csdn.net/qq_28550263/article ...

  9. js object 、 json转换

    一.object to json function obj2str(o){ var r = []; if(typeof o =="string") return "\&q ...

最新文章

  1. Java中的基本类型变量储存方式
  2. mysql隔离级别加锁情况_MySQL数据库事务各隔离级别加锁情况--read committed amp;amp; MVCC...
  3. 柏堰工业园有做机器人的吗_合肥柏堰科技园推进机器人应用 促产业转型升级...
  4. 安装smac的注意事项
  5. how is my Angular custom controller code executed
  6. hive in 字符串_hive常用函数 concat concat_ws row_number
  7. MySQL 5.5 手册下载
  8. 5个python标准库及作用_零基础编程——Python标准库使用
  9. 谷歌浏览器自带的翻译插件为什么不能用?
  10. 苹果cms function.php,送你们几个字!对!就是MACCMS注入!
  11. UC浏览器设置代理服务器JAVA_uc浏览器让JAVA手机变“聪明”的方法
  12. [Keil][Verilog][微机原理] 流水灯、存储器、外部中断实验_北京邮电大学计算机原理与应用课程实验报告
  13. js截取图片 裁剪图片之cropper.js插件用法详解
  14. xilinx IP 汇总
  15. 深度评测 华米Amazfit跃我GTR3和GTR3 Pro选哪个
  16. 最适合读研的5大城市:北上广上榜,另外两个城市原来是……
  17. 2014校园招聘_百度2014校园招聘
  18. 数据分析(二)----- 描述性统计分析
  19. 这是我见过最牛的报表制作神器!比Excel强大20倍!
  20. 如何理解数列极限的定义?

热门文章

  1. 如何批量合并Excel文件和工作表 - Excel合并器使用教程
  2. excel 合并多个工作表,保留列宽等格式
  3. HTTP服务器开发教程
  4. java实现平衡二叉树
  5. 51单片机:数码管(静态+动态)
  6. 十进制与二进制的转换
  7. python可以用保留字作为函数的名字吗_Python不允许使用关键字作为变量名,允许使用内置函数名作为变量名,但这会改变函数名的含义...
  8. 基于改进yolov4和unet的飞机目标的分割
  9. node之request模块
  10. oracle01109,oracle解锁用户时报错ORA-01109: database not open的解决办法