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一个对象赋值给另一个对象,支持平铺类和层级类间的互转

缓存友好的优化:面向对象的矩阵乘法和函数内平铺矩阵乘法

在 SceneKit 中,如何在不同大小的对象上平铺纹理,同时保持最小的绘制调用?

qt MDI如何实现垂直平铺

css 背景平铺