1. familiarJSON.stringify()
In the browser or server, JSON.stringify()
it is our very commonly used method:
- The JSON object stored in localStorage;
- JSON body POST request;
- Processing JSON body form data response;
- Even under certain conditions, we will use it to achieve a simple deep copy;
- ……
In some performance-sensitive applications (such as server handle a large number of concurrent), or when the face of a large number of stringify operation, we will hope to better its performance, faster. It also spawned a number of optimization programs stringify / library, it is the performance comparison thereof with FIG green original method:
Native green part of the JSON.stringify()
visible compared to the performance of these libraries should be much lower. What, then, to enhance the technical principles behind the sharp performance is it?
2. Than stringify
Fasterstringify
Because JavaScript is very dynamic language, so for a variable of type Object, key, key, key types it contains can only be determined at run time. Therefore, the implementation JSON.stringify()
will have a lot of work to do when. In the case of ignorance, we want to significantly optimize apparently powerless.
So if we know that in the Object keys, key information it - that is, to know its structure information, which will help it?
Look at an example:
The following Object,
const obj = {
name: 'alienzhou',
status: 6,
working: true
};
We use it JSON.stringify()
to obtain results
JSON.stringify(obj);
// {"name":"alienzhou","status":6,"working":true}
Now if we know that obj
the structure is fixed:
- Keys unchanged
- Certain types of keys
Well actually, I can create a "customized" method of stringify
function myStringify(o) {
return (
'{"name":"'
+ o.name
+ '","status":'
+ o.status
+ ',"isWorking":'
+ o.working
+ '}'
);
}
Check out our myStringify
output method:
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}
Get the right result, but only uses the type conversion and string concatenation, so "customized" approach allows "stringify" faster.
Overall, we compared how to get stringify
a faster stringify
way to do that?
- Structural information required to determine the object;
- According to its configuration information to create an object that such structure "Custom" of the
stringify
method, which is generated inside the actual results by the string concatenation; - Finally, use the "customized" approach to stringify objects can be.
This is also the most stringify accelerate routine libraries into code that is similar to:
import faster from 'some_library_faster_stringify';
// 1. 通过相应规则,定义你的对象结构
const theObjectScheme = {
// ……
};
// 2. 根据结构,得到一个定制化的方法
const stringify = faster(theObjectScheme);
// 3. 调用方法,快速 stringify
const target = {
// ……
};
stringify(target);
3. How to generate a "customized" approach
According to the above analysis, the core functionality that according to its information structure, the class create "customized" stringify method of object, its interior is actually simple property access to the string concatenation.
In order to understand the specific implementation, following me to achieve slightly different on the two open source libraries as an example to briefly explain.
3.1. fast-json-stringify
The figure is in accordance with FAST-JSON-the stringify Benchmark results provided, sort out performance comparison.
We can see, have 2-5 times the performance in most scenarios.
3.1.1. Scheme defined way
fast-json-stringify using JSON Schema Validation to define the data format (JSON) object. Scheme which itself is defined by the structure of JSON format, such as object
{
name: 'alienzhou',
status: 6,
working: true
}
Corresponding scheme is:
{
title: 'Example Schema',
type: 'object',
properties: {
name: {
type: 'string'
},
status: {
type: 'integer'
},
working: {
type: 'boolean'
}
}
}
Its rich scheme defined rules, refer to the specific use Ajv this check JSON library.
3.1.2 Method of generating stringify
fast-json-stringify just defined based scheme, the actual splicing generates the function code string, and then use the Function constructor dynamically generated corresponding stringify functions at runtime.
In the code generation, first of all it will inject pre-defined method for all kinds of tools, different portions of the scheme is the same:
var code = `
'use strict'
`
code += `
${$asString.toString()}
${$asStringNullable.toString()}
${$asStringSmall.toString()}
${$asNumber.toString()}
${$asNumberNullable.toString()}
${$asIntegerNullable.toString()}
${$asNull.toString()}
${$asBoolean.toString()}
${$asBooleanNullable.toString()}
`
Next, the specific code is generated based on the specific contents of the function stringify defined scheme. Generated relatively simple way: by walking scheme.
Traversal scheme, according to a defined type, inserted into the corresponding utility function to convert code corresponding to the key value. For example, in the above example name
this property:
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
……
The above code code
stored in the last variable is the code string generated function body. Since the definition scheme, name
as the string
type, and does not, it will be empty code
adding a code string as follows:
"json += $asString(obj['name'])"
Since the need to handle complex situations arrays, and the like with an object, the actual code number is omitted.
Then, the resulting complete code
string is as follows:
function $asString(str) {
// ……
}
function $asStringNullable(str) {
// ……
}
function $asStringSmall(str) {
// ……
}
function $asNumber(i) {
// ……
}
function $asNumberNullable(i) {
// ……
}
/* 以上是一系列通用的键值转换方法 */
/* $main 就是 stringify 的主体函数 */
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
Finally, the code
string passed in Function constructor to create the appropriate stringify function.
// dependencies 主要用于处理包含 anyOf 与 if 语法的情况
dependenciesName.push(code)
return (Function.apply(null, dependenciesName).apply(null, dependencies))
3.2. slow-json-stringify
slow-json-stringify Although the name is "slow", but in fact is a "fast" of stringify library (named very naughty).
The slowest stringifier in the known universe. Just kidding, it's the fastest (:
Its implementation is more lightweight than the fast-json-stringify mentioned earlier, the idea is very clever. At the same time it efficiency faster than fast-json-stringify in many scenes .
3.2.1. Scheme defined way
scheme defined slow-json-stringify more natural and simple, the main key is to replace the type described. Or the case of the above object, scheme becomes
{
name: 'string',
status: 'number',
working: 'boolean'
}
Really very intuitive.
3.2.2 Method of generating stringify
I do not know if you noticed
// scheme
{
name: 'string',
status: 'number',
working: 'boolean'
}
// 目标对象
{
name: 'alienzhou',
status: 6,
working: true
}
Construction scheme and the original object is not like?
The trick of this scheme is that, after this definition, we can put the scheme JSON.stringify
a bit, then "deduction" all types of values, and finally waiting for us is to fill the actual value of the scheme directly to the corresponding type declaration.
Specifically, how does it work?
First, the scheme can be directly invoked JSON.stringify()
to generate a base template, while borrow JSON.stringify()
the second argument as the access path traversal attribute collected:
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;
});
At this time, map
in the access path to collect all the attributes. Simultaneously generated props
can be spliced to match the corresponding type of character is also a regular expression, such as our example of regular expression /name|status|working"(string|number|boolean|undef)"|\\[(.*?)\\]/
.
Then, according to the regular expression to match the order of these attributes, the attribute type replace the string, the string into uniform footprint "__par__"
, based on "__par__"
splitting the string:
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__');
In this way you will get chunks
and the props
two arrays. chunks
Contains a JSON string is split. In the example, the two arrays are as follows
// chunks
[
'{"name":"',
'","status":"',
'","working":"',
'"}'
]
// props
[
'name',
'status',
'working'
]
Finally, since the map stored in the map attribute name and access path can be accessed according to the value of a property of the prop object, looping through the array, which is spliced to the corresponding chunks.
From an implementation point of view and the code amount, the program will be lighter and ingenious, they also do not need to execute a function or dynamically generated by a Function, eval like.
4. Summary
Although the realization of various libraries are different, but the overall idea, the way to achieve high performance stringify are the same:
- Developer defines JSON scheme Object of;
- stringify corresponding template library generation methods, a method where the template attributes and values based scheme will be string concatenation (Obviously, the efficiency of splicing string attribute access to much higher);
- Finally, the developer calls the method returns to stringify Object can be.
In the final analysis, it is essentially static structural information and analysis to optimize the front.
Tips
Finally, still I would like to mention
- All benchmark only as a reference, if there are specific performance, improve how much it is recommended that you test in the actual business;
- Use fast-json-stringify into the Function constructor is not recommended as a direct user input scheme, in case some security issues.
Reproduced in: https: //www.jianshu.com/p/f022f81970ae