构建JSON序列化后符号变化的方案 问题描述 在进行JSON序列化时,有时候会出现符号变化的问题,例如双引号或单引号被转义或丢失的情况。情况是这样的,我在作配置生成器过程中用到了默认配置, 属性用做内容填充对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 默认配置 const configsData = { "1_minigame": { host: `www.a0.com`, data: { name: `A0`, logo: `${templateName}/a0` }, html: { htmlTest:``, hd_index:``, hd_details:``, hd_category:``, hd_play:``, ad0_0:``, ad0_1:``, ad0_2:``, ad0_3:``, ad0_4:``, ad1_0:``, ad1_1:``, ad1_2:``, } } };
期间我创建了原配置对象的深层副本,输出内容后,反引号变成了双引号,
1 2 const templateConfig = JSON.parse(JSON.stringify(configsData[templateName])); console.log(templateConfig)
于是在做填充内容时,双引号里面的内容要是包含双引号或单引号,放到编译器编译器就会报错。
解决思路: 创建递归函数formatConfigObject,将格式化嵌套对象转换为字符串表示形式. 通过 Object.keys方法遍历 keys 数组中的每个prop属性,在遍历过程中,检查prop类型。如果值的类型是对象,并且不为 null,则说明该属性的值是一个嵌套的配置对象。函数会递归调用 formatConfigObject 方法,将缩进级别加一,并将嵌套对象的字符串表示添加到 configString 中。 如果值的类型不是对象,或者为 null,则说明该属性的值是基本类型,函数会根据值的类型进行处理。如果值的类型是字符串,则在其外部添加反引号;并添加到configString里去。
最后,函数根据当前属性的索引位置判断是否为最后一个属性,如果不是,则在 configString 的末尾添加换行符 \n。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function formatConfigObject(obj, indent = 1) { const tabs = '\t'.repeat(indent); let configString = ''; const keys = Object.keys(obj); const lastKeyIndex = keys.length - 1; keys.forEach(function (prop, index) { const value = obj[prop]; if (typeof value === 'object' && value !== null) { configString += tabs + prop + ': {\n' + formatConfigObject(value, indent + 1) + tabs + '},'; } else { const formattedValue = '`' + value + '`'; configString += tabs + prop + ': ' + formattedValue + ','; } if (index !== lastKeyIndex) { configString += '\n'; } }); return configString; }
期间饶了两个弯,第一次的想法就是遍历将转换后的字符串双引号替为反引号
1 configString = configString.replace(/"/g, "`");
这样内容会被影响到。
后面换了种想法直接修改要填充的内容的反引号和双引号为单引号,这样不仅会修改到内容,而且效率低麻烦。
反解:将字符串转为Json对象 通过之前的方法将json转为string类型,成功得到了我们想要的配置,假如直接复制到配置脚本没什么问题,但是我们要写个自动推送器,又要使用的configString字符串里子属性,这时就要把他反解开。
例如我接收到的配置String类型
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { host: ``, data: { name: ``, logo: `` }, html: { htmlTest: `test`, hd_index: ``, hd_details: ``, hd_category: ``, hd_play: ``, ad0_0: ``, ad0_1: ``, ad0_2: ``, ad0_3: ``, ad0_4: ``, ad1_0: ``, ad1_1: ``, ad1_2: `` } }
之前我们做的操作就是把属性内容符号转为反引号方便添加配置,考虑到还原后的配置属性里也嵌套着json结构,有点复杂。说说我的思路:
1 2 3 let dataValues = getValueBy(configString) const parsedConfig = parseConfigString(dataValues.emptyConfig); let njsonConfig = fillValues(parsedConfig, dataValues.values)
在接收到配置的第一时间,我们可以通过getValueBy
遍历反引号内容,把反引号里值抽出来存放到values
数组,顺便保留去掉值后的字符串emptyConfig
,然后对emptyConfig
处理为json再重新赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function getValueBy(config) { //把反引号里的值抽出来放到values let values = []; const backtickMatches = config.match(/`([^`]*)`/g); if (backtickMatches) { backtickMatches.forEach(match => { const backtickContent = match.slice(1, -1).trim(); values.push(backtickContent); }); } //获取去掉values后的config let emptyConfig = getEmptyKey(config) return { values, emptyConfig }; } function getEmptyKey(config) { let result = ''; let doubletag = 0 for (let i = 0; i < config.length; i++) { if (config[i] === '`') { result += config[i] doubletag += 1 } else if (doubletag % 2 === 0) { result += config[i] } } return result.trim(); }
当然方法有很多,你也可以在getEmptyKey遍历过程中加个反锁再把values抽出来。
在获得values和emptyConfig后,使用parseConfigString(configString)
方法将emptyConfig
转为json对象,好处就是转换过程中不会影响到属性值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function parseConfigString(configString) { // 没用左括号,退出 if (!configString.includes('{')) { return; } let configObj = {}; const delBigKuo = configString.slice(1, -1); // 按逗号切割 let keyValuePairs = delBigKuo.trim().split(','); let temp = ''; keyValuePairs.forEach(pair => { temp += pair; if ((temp.match(/{/g) || []).length === (temp.match(/}/g) || []).length) { const colonIndex = temp.indexOf(':'); if (colonIndex !== -1) { const cleanedKey = temp.slice(0, colonIndex).replace(/`/g, '').trim(); let cleanedValue = temp.slice(colonIndex + 1).trim(); if (cleanedValue.startsWith('`') && cleanedValue.endsWith('`')) { cleanedValue = cleanedValue.slice(1, -1); } if (cleanedValue[0] === '{') { cleanedValue = parseConfigString(cleanedValue); } configObj[cleanedKey] = cleanedValue } temp = ''; } else { temp += ','; } }); return configObj; }
将转换为json的parsedConfig和values传到fillValue(jsonObj, values)
重新赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function fillValues(jsonObj, values) { let i = 0; for (let key in jsonObj) { if (jsonObj.hasOwnProperty(key)) { if (typeof jsonObj[key] === 'object') { jsonObj[key] = fillValues(jsonObj[key], values.slice(i)); //从values的第i位开始新数组切片 i += countValues(jsonObj[key]); } else { jsonObj[key] = values[i]; i++; } } } return jsonObj; } // 计算对象中的属性数量 function countValues(obj) { let count = 0; for (let key in obj) { if (obj.hasOwnProperty(key)) { count++; } } return count; }
在反解过程尝试了很多种解法都失败了,因为反引号里属性值内容的不确定性会有很多影响,最直接的方法就是分开处理值和键再合并,至此完毕!!