推广 热搜: csgo  vue  2023  angelababy  gps  信用卡  新车  htc  落地  控制 

object转化为json 如何提升JSON.stringify()的性能?

   2023-08-17 网络整理佚名2870
核心提示:加速库的套路,转化为代码就是类似:时,根据定义的类型,在对应代码处插入相应的工具函数用于键值转换。上面代码中的code变量保存的就是最后生成的函数体的代码串。定义更自然与简单,主要就是将键值替换为类型描述。()的第二个参数来作为遍历方法收集属性的访问路径:同时生成的props可以拼接为匹配相应类型字符还的正则表达式,例如我们这个例子里的正则表达式为/name||(|||undef)|\\[(.

来源 |

1.熟悉的JSON.()

在浏览器端或者服务器端,JSON.()是一个非常常用的方法:

在一些性能敏感的情况下(例如服务器处理大量并发),或者面对大量操作时,我们希望它的性能更好,速度更快。 这也导致了一些优化的解决方案/库。 下图展示了它们与原生方法的性能对比:

绿色部分是原生的JSON.(),可见性能比这些库低很多。 那么,性能大幅提升背后的技术原理是什么呢?

2. 比

由于它是一种高度动态的语言,对于一种类型的变量,其包含的键名、键值、键值类型只能在运行时确定。 所以执行 JSON.() 的时候还有很多工作要做。 在不了解任何情况的情况下,如果我们想要大幅优化,显然无能为力。

那么如果我们知道这里面的键名和键值信息——也就是知道它的结构信息,这会有帮助吗?

看一个例子

在此之下,

const obj = {
name: 'alienzhou',
status: 6,
working: true
};

我们对其应用 JSON.() ,结果是

JSON.stringify(obj);
// {"name":"alienzhou","status":6,"working":true}

现在如果我们知道这个 obj 的结构是固定的:

好吧,实际上,我可以创建一个“自定义”方法

function myStringify(o) {
return (
'{"name":"'
+ o.name
+ '","status":'
+ o.status
+ ',"isWorking":'
+ o.working
+ '}'
);
}

看一下我们方法的输出:

myStringify({
name: 'alienzhou',
status: 6,
working: true
});
// {"name":"alienzhou","status":6,"isWorking":true}

myStringify({
name: 'mengshou',
status: 3,
working: false
});
// {"name":"mengshou","status":3,"isWorking":false}

可以得到正确的结果,但只用到了类型转换和字符串拼接,所以“自定义”的方法可以让“”更快。

综上所述,如何获得比

这也是大多数加速库的套路,代码类似:

import faster from 'some_library_faster_stringify';

// 1. 通过相应规则,定义你的对象结构
const theObjectScheme = {
// ……
};

// 2. 根据结构,得到一个定制化的方法
const stringify = faster(theObjectScheme);

// 3. 调用方法,快速 stringify
const target = {
// ……
};
stringify(target);

3. 如何生成“自定义”方法

根据上面的分析,其核心功能就是根据该类对象的结构信息为其创建一个“定制”的方法,其内部其实就是简单的属性访问和字符串拼接。

为了便于理解具体的实现,我简单介绍一下两个实现稍有不同的开源库作为例子。

3.1. 快速-json-

下图是根据fast-json-提供的结果进行的性能对比。

可以看到,在大多数场景下,都有2-5倍的性能提升。

3.1.1. 如何定义

fast-json - 使用JSON来定义(JSON)对象的数据格式。它本身定义的结构也是JSON格式的,比如对象

{
name: 'alienzhou',
status: 6,
working: true
}

对应的是:

{
title: 'Example Schema',
type: 'object',
properties: {
name: {
type: 'string'
},
status: {
type: 'integer'
},
working: {
type: 'boolean'
}
}
}

其定义规则丰富。 具体使用请参考Ajv,一个JSON验证库。

3.1.2. 方法生成

fast-json-会根据刚才的定义生成实际的函数代码字符串,然后在运行时使用构造函数动态生成对应的函数。

在代码生成方面,首先它会注入各种预定义的工具方法,这些方法都是一样的:

var code = `
'use strict'

`

code += `
${$asString.toString()}
${$asStringNullable.toString()}
${$asStringSmall.toString()}
${$asNumber.toString()}
${$asNumberNullable.toString()}
${$asIntegerNullable.toString()}
${$asNull.toString()}
${$asBoolean.toString()}
${$asBooleanNullable.toString()}
`

其次,会根据定义的具体内容生成函数的具体代码。 而且生成的方式也比较简单:通过遍历。

遍历时,根据定义的类型,在对应的代码处插入对应的工具函数,进行键值转换。 例如上例中的name属性:

var accessor = key.indexOf('[') === 0 ? sanitizeKey(key) : `['${sanitizeKey(key)}']`
switch (type) {
case 'null':
code += `
json += $asNull()
`

break
case 'string':
code += nullable ? `json += obj${accessor} === null ? null : $asString(obj${accessor})` : `json += $asString(obj${accessor})`
break
case 'integer':
code += nullable ? `json += obj${accessor} === null ? null : $asInteger(obj${accessor})` : `json += $asInteger(obj${accessor})`
break
……

上面代码中的code变量保存的是最后生成的函数体的代码串。 由于在定义中,name是一个类型并且不为空,因此将在代码中添加以下代码字符串:

"json += $asString(obj['name'])"

由于需要处理数组、链接对象等复杂情况,所以省略了很多实际代码。

那么生成的完整代码串大致如下:

function $asString(str) {
// ……
}
function $asStringNullable(str) {
// ……
}
function $asStringSmall(str) {
// ……
}
function $asNumber(i) {
// ……
}
function $asNumberNullable(i) {
// ……
}



function $main(input) {
var obj = typeof input.toJSON === 'function'
? input.toJSON()
: input

var json = '{'
var addComma = false
if (obj['name'] !== undefined) {
if (addComma) {
json += ','
}
addComma = true
json += '"name":'
json += $asString(obj['name'])
}

// …… 其他属性(status、working)的拼接

json += '}'
return json
}

return $main

最后,将代码字符串传递到构造函数中以创建相应的函数。

// dependencies 主要用于处理包含 anyOf 与 if 语法的情况
dependenciesName.push(code)
return (Function.apply(null, dependenciesName).apply(null, dependencies))

3.2. 慢-json-

Slow-json——虽然名字是“慢”,但它实际上是一个“快”的库(名字很调皮)。

众所周知。 只是,它是 (:

它的实现比前面提到的fast-json-更加轻量级,而且想法也非常巧妙。 同时,在很多场景下它的效率会比fast-json-更快。

3.2.1. 如何定义

Slow-json-的定义更加自然简单,主要是将键值替换为类型描述。还是上面对象的例子,就会变成

{
name: 'string',
status: 'number',
working: 'boolean'
}

确实非常直观。

3.2.2. 方法生成

不知道你注意到了吗

// scheme
{
name: 'string',
status: 'number',
working: 'boolean'
}

// 目标对象
{
name: 'alienzhou',
status: 6,
working: true
}

是不是和原来物体的结构很相似?

这样做的巧妙之处在于,在这个定义之后,我们可以把 . 首先,然后“扣除”所有类型值,最后等待我们的就是将实际值直接填充到相应的类型声明中。

怎么做?

首先可以直接调用JSON.()生成基础模板,借用JSON.()的第二个参数作为遍历方法的访问路径来收集属性:

let map = {};
const str = JSON.stringify(schema, (prop, value) => {
const isArray = Array.isArray(value);
if (typeof value !== 'object' || isArray) {
if (isArray) {
const current = value[0];
arrais.set(prop, current);
}

_validator(value);

map[prop] = _deepPath(schema, prop);
props += `"${prop}"|`;
}
return value;
});

至此,所有属性的访问路径就都收集到了map中。 同时,生成的props可以拼接成匹配相应类型字符的正则表达式。 例如,我们示例中的正则表达式为 /name||"(|||undef)"|\\[(.*?)\\ ]/。

然后,根据正则表达式按顺序匹配这些属性,将属性类型的字符串替换为统一的占位符字符串“”,并根据“”分割字符串:

const queue = [];
const chunks = str
.replace(regex, (type) => {
switch (type) {
case '"string"':
case '"undefined"':
return '"__par__"';
case '"number"':
case '"boolean"':
case '["array-simple"]':
case '[null]':
return '__par__';
default:
const prop = type.match(/(?<=\").+?(?=\")/)[0];
queue.push(prop);
return type;
}
})
.split('__par__');

这样你就会得到两个数组和道具。 包含分割后的JSON字符串。以两个数组为例,如下

// chunks
[
'{"name":"',
'","status":"',
'","working":"',
'"}'
]

// props
[
'name',
'status',
'working'
]

最后,由于map中保存了属性名称与访问路径的映射关系,因此可以根据prop访问对象中的某个属性的值,循环遍历数组,与对应的进行拼接。

从代码量和实现上来看,这种方案会更轻量、更巧妙,而且不需要通过eval等方法动态生成或执行函数。

4. 总结

虽然不同库的实现不同,但从整体思路来看,实现高性能的方式是相同的:

归根结底,它本质上是通过静态结构信息来预设优化和分析。

尖端

最后我还想提一下

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON