JSON对象平铺和平铺后对象化的实现学习
Posted FserSuN
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JSON对象平铺和平铺后对象化的实现学习相关的知识,希望对你有一定的参考价值。
1.什么是JSON对象平铺与对象化
常见的json结构如下:
let jdata = {
"a": "a1",
"b": {
"b1": "b11"
},
"c": ["c1", "c2", "c3"],
"d": [{
"d1": "d2"
}]
}
平铺即用单层的 (Key,Value) 结构表示当前这种递归结构,平铺后效果如下:
{
"a":"a1",
"b.b1":"b11",
"c[0]":"c1",
"c[1]":"c2",
"c[2]":"c3",
"d[0].d1":"d2"
}
而平铺对象化就是将原来的平铺结构还原为原始对象。
2. 平铺的实现
json对象中的v可以是一个对象、数组、基本数值类型。而数组中的v依然满足第一条定义。
以第一节的结构为例,平铺过程既是递归的解析每个v,直到v是一个基本结构(数字、字符串、布尔值)不可拆分。随后用路径作为key,最后不可分的值作为v。
根据上述思路代码实现如下:
Object.jsonFlatten = function(data) {
let result = {};
function deepTraverse(obj, path) {
if (obj !== Object(obj)) {
result[path] = obj;
return;
}
// 数组对象,如果前缀路径保证正确,拼接上下标访问符即可
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
deepTraverse(obj[i], path + "[" + i + "]");
}
return;
}
// json对象
for (let i in obj) {
if (path !== "") {
deepTraverse(obj[i], path + "." + i);
} else {
deepTraverse(obj[i], i);
}
}
}
if(data !== Object(data)) {
return data;
}
deepTraverse(data,"");
return result;
}
3.平铺数据对象化的实现
平铺后得到的结果是一个单层的(key,value)结构。对象化即是将key还原成
原始的对象结构。
首先分析key的组成分如下几种:
- 嵌套对象结构:x.y.z
- 对象加数组: x.y[0],
- 对象加嵌套数组: x.y[0][0]
接着我们对key进行解析,拆分处我们要的元素, 对象或数组。
以x.y[0]为例:
我们需要拆分出 x、y、[0]三个元素。由于key是一个字符串,因此需要做一个简单的词法分析。通过正则表达式,解析出需要的词。
限定对象名只能是 非 . [ ]三个字符之外的符号构成,
x 可以是一个对象 .y 结构中的y是一个对象,[0]是一个数组。
因此解析分两类:
\\.?([^.\\[\\]]+) 解析 x 、.y对象结构
\\[\\d+\\] 解析数组
对于一串符号,我们期待每解析出一个元素,就去还原我们需要的结构。对于x.y[0]还原过程如下:
解析出x
{
x: {
}
}
解析出 y
{
x: {
y:{
}
}
}
解析出[0]
{
x: {
y:[0]
}
}
最后通过当前指针修改数组中的值。
这时我们发现一个问题 解析出 y -> [0]的过程,按正常顺序解析出y,没法直接判断y的value是一个对象还是数组。 因此我们需要前看一个符号进行判断。 或者转换思考,我们所填的对象均是某个key的value中存放的对象的一个key。
这样转换思考后,b[0],这种形式就可以统一进行处理了。
本例中,解析出y时,我们前看一个符号 [0],发现是数组,这时y的value填充为数组,反之填充为对象。
为了统一化处理问题,我们引入一个初始对象{} 和一个默认的key “” (换成任意不冲突符号都可以)
初始:
{
"":
}
随后党我们解析目标key(x.y[0])时, 每遇到一个符号就相当于是前看符号的作用。
遇到x
{
"": {
}
}
遇到y
{
"":{
x:{
}
}
}
遇到[0]
{
"":{
x:{
y:[]
}
}
}
最后一步填充值,原来的key,拿到值,填充到 最后的数组中[]。
上述描述清楚了思路,如问期望所述,我们期望每个符号解析到了,在字符列表中继续解析下一个符号。这个能力在不同语言中实现不同。以javascript为例,我们会用到正则表达式的exec方法。exec方法[1]说明如下,我们写正则表达式时,需要用到全局匹配。
exec
说明
exec() 方法的功能非常强大,它是一个通用的方法,而且使用起来也比 test() 方法以及支持正则表达式的 String 对象的方法更为复杂。
如果 exec() 找到了匹配的文本,则返回一个结果数组。否则,返回 null。此数组的第 0 个元素是与正则表达式相匹配的文本,第 1 个元素是与 RegExpObject 的第 1 个子表达式相匹配的文本(如果有的话),第 2 个元素是与 RegExpObject 的第 2 个子表达式相匹配的文本(如果有的话),以此类推。除了数组元素和 length 属性之外,exec() 方法还返回两个属性。index 属性声明的是匹配文本的第一个字符的位置。input 属性则存放的是被检索的字符串 string。我们可以看得出,在调用非全局的 RegExp 对象的 exec() 方法时,返回的数组与调用方法 String.match() 返回的数组是相同的。
但是,当 RegExpObject 是一个全局正则表达式时,exec() 的行为就稍微复杂一些。它会在 RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。
通过上述分析javascript实现如下:
Object.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\\.?([^.\\[\\]]+)|\\[(\\d+)\\]/g,
resultholder = {};
for (var p in data) {
var cur = resultholder,
prop = "",
m;
while (m = regex.exec(p)) {
cur = cur[prop] || (cur[prop] = (m[2] ? [] : {}));
prop = m[2] || m[1];
}
cur[prop] = data[p];
}
return resultholder[""] || resultholder;
};
4. 总结
分析平铺前的结构特点和平铺后的结构特点,在还原时利用额外数据简化前看的操作,简化代码
参考
[1]exec方法说明,https://www.w3school.com.cn/jsref/jsref_exec_regexp.asp
[2]https://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects
以上是关于JSON对象平铺和平铺后对象化的实现学习的主要内容,如果未能解决你的问题,请参考以下文章
使用vue学习three.js之加载和使用纹理- 通过设置纹理的wrapSwrapTrepeat属性实现纹理的重复平铺,纹理的重复映射
java一个对象赋值给另一个对象,支持平铺类和层级类间的互转