一、JSON.stringify作用
将一個對象或值轉換成一個JSON字元串。
console.log(JSON.stringify({ x: 5, y: 6 }));
// "{"x":5,"y":6}"
console.log(JSON.stringify(true));
// "true"
console.log(JSON.stringify(new Date()));
// 2020-03-01T13:42:52.105Z
console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] }));
// "{"x":[10,null,null,null]}"
二、參數
有三個參數:
- value:要轉換的資料
- replacer: 如果是函數,則在轉換過程中每個屬性的轉換都要調用該函數;如果是數組則隻轉換數組中包含的屬性,如果是null則轉換所有屬性。
- space:該參數用于美化json字元串,提高可讀性。如果是數字表示空格的個數,最多10個,小于1表示沒有空格;是字元串,則用字元串填充。
const data = {
a: 1,
b: {
c: 2,
}
}
const replacer = function(key, value) {
if (typeof value === 'number') {
return value * 2;
}
return value;
}
console.log(JSON.stringify(data, replacer, 4));
//"{
// "a": 2,
// "b": {
// "c": 4
// }
//}"
上面值為number類型時,都乘以2,同時前面填充4個空格。
const data = {
a: 1,
b: {
c: 2,
}
}
const replacer = function(key, value) {
if (typeof value === 'number') {
return value * 2;
}
return value;
}
console.log(JSON.stringify(data, replacer, "-"));
//"{
//-"a": 2,
//-"b": {
//--"c": 4
//-}
//}"
三、轉換規則
- 如果資料中有toJSON方法,則使用toJSON傳回的值。 比如Date對象就自帶toJSON方法。
const data = { a: 1, b: 2, toJSON: function() { return this.a + this.b; } } // '3'
- Boolean、Number和String都會轉換成相應的原始值。
const data = { a: 1, b: '2', c: true, }; // '{"a":1,"b":"2","c":true}'
- undefined、function、Symbol對于JSON都不是有效的值,是以都傳回undefined;如果在對象中對應的屬性将會被忽略;在數組中都轉換成null;
JSON.stringify(undefined) // undefined JSON.stringify(function(){}) // undefined JSON.stringify(Symbol('foo')) // undefined const data = { a: undefined, b: function() {}, c: Symbol('foo'), d: [true, undefined, function(){}, Symbol.for('foo')], }; //'{"d":[true,null,null,null]}'
- Symbol作為對象的key時,忽略該屬性
const data = { a: 1, [Symbol.for('foo')]: 'baz' } //'{"a":1}'
- Date自帶toJSON方法,傳回的值跟toISOString相同。
- Infinity、NaN和null都會被認為是null。
const data = { a: 1, b: Infinity, c: NaN, d: null, }; // '{"a":1,"b":null,"c":null,"d":null}'
- 其他對象(Map、Set、WeakSet、WeakMap等)都會序列化其可枚舉屬性。
const data = { a: 1, }; Object.defineProperty(data, 'b', { value: 2, enumerable: false, configurable: true, }); // '{"a":1}' JSON.stringify(new Set([1])); // '{}' // 使用Object.keys(new Set([1])),擷取不到任何屬性
- BigInt類型不能序列化,會報類型錯誤
JSON.stringify({x: 1n}); //Uncaught TypeError: Do not know how to serialize a BigInt
四、手寫JSON.stringify
将以前自己寫的實作方式整理了一下,代碼如下:
function stringify(data, replacer = null, space = '') {
const toString = Object.prototype.toString;
const types = {
'[object String]': 'string',
'[object Number]': 'number',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Boolean]': 'bool',
'[object Symbol]': 'symbol',
'[object BigInt]': 'bigInt',
'[object Object]': 'object',
'[object RegExp]': 'regexp',
'[object Date]': 'date',
'[object Set]': 'set',
'[object Map]': 'map',
};
/**
*
* @param {*} data
* 擷取資料類型
*/
const getType = data => {
return types[toString.call(data)];
}
/**
*
* @param {*} key
* @param {*} value
*
* 處理stringify的replacer
* 如果是函數,直接調用函數,
* 如果是數組:沒有key時或者包含key時傳回資料本身,否者傳回undefined;
*/
const replacerCallback = function (key = '', value) {
let type = getType(replacer);
switch (type) {
case 'function':
return replacer(key, value);
case 'array':
return ('' === key || replacer.includes(key)) ?
value :
undefined;
default:
return value;
}
}
/**
* 擷取填充字元串
*/
const getSpaceStr = function () {
if (getType(space) === 'number') {
return Array(space).fill(' ').join('');
}
if (getType(space) === 'string') {
return space;
}
return '';
}
// 初始化填充字元串;
const spaceStr = getSpaceStr();
const needBeautify = spaceStr.length > 0 ? true : false;
// 擷取填充的字元串
const getPadStr = (level) => "\n" + Array(level).fill(spaceStr).join('');
// 格式化字元串
const formatResult = function (data, level = 0) {
if (!needBeautify || 0 === level) {
return data;
}
return getPadStr(level) + data;
}
/**
*
* @param {*} data
* @param {*} level
* 格式化對象
*/
const formatObject = function (data, level = 0) {
if (data.length === 0) {
return formatResult('{}', level);
}
return formatResult('{', level) +
data.map(item => formatResult(item, level + 1))
+ ((needBeautify && level === 0) ? "\n" : '') +
formatResult('}', level);
}
/**
*
* @param {*} data : 資料本身
* @param {*} level: 資料所在的嵌套層數
* 格式化數組
*/
const formArray = function(data, level = 0) {
if (data.length === 0) {
return formatResult('[]', level);
}
const res = ('['
+ data.map(item => formatResult(item, level + 1))
+ ((needBeautify && level === 0) ? "\n" : '') +
formatResult(']', level));
const reg = new RegExp(getPadStr(level + 1) + getPadStr(level + 1), 'g');
const newStr = getPadStr(level + 1);
return res.replace(reg, newStr);
}
/**
*
* @param {*} key : 資料的鍵
* @param {*} origin : 資料的值
* @param {*} level : 資料在原始資料中的嵌套層數
*/
const translate = function (key, origin, level) {
let data = replacerCallback(key, origin);
switch (getType(data)) {
case 'bool':
return `${data}`;
case 'string':
return `"${data}"`;
case 'number':
if (data == Infinity || isNaN(data)) {
return 'null';
}
return `${data}`;
case 'undefined':
case 'function':
case 'symbol':
return 'undefined';
case 'null':
return 'null';
case 'bigInt':
throw new TypeError('BigInt can not seriallied');
case 'array':
const result = data.map((item, index) => {
let res = translate('', item, level + 1);
switch (res) {
case 'undefined':
return 'null';
default:
return res;
}
});
return formArray(result, level);
default:
if ('toJSON' in data) {
return data.toJSON();
}
let res = [];
for (let [key, value] of Object.entries(data)) {
if ('symbol' === getType(key)) {
continue;
}
let tmp = translate(key, value, level + 1);
if ('undefined' === tmp) {
continue;
}
// 對象需要格式化時,value前面有一個空格
res.push(`"${key}":${needBeautify ? ' ' : ''}${tmp}`);
}
return formatObject(res, level);
}
}
return translate('', data, 0);
}
// 測試資料
const data = {
a: 2,
b: {
y: 'y',
},
c: Symbol('foo'),
[Symbol.for('foo')]: 'd',
e: function(){},
x: [
{
y: 'y',
a: '3'
},
{
p: 'p',
q: 'q',
},
undefined,
null,
NaN,
"foo",
new Set([1]),
new WeakSet(),
new Map([{a: 1}]),
new WeakMap([[{a:1}, 2]]),
function(){},
],
};
const replacer = function (key, value) {
if (typeof value === 'number') {
return 2 * value;
}
return value;
}
console.log(stringify(data));
//'{"a":2,"b":{"y":"y"},"x":[{"y":"y","a":"3"},{"p":"p","q":"q"},null,null,null,"foo",{},{},{},{},null]}'
console.log(stringify(data, replacer));
//'{"a":4,"b":{"y":"y"},"x":[{"y":"y","a":"3"},{"p":"p","q":"q"},null,null,null,"foo",{},{},{},{},null]}'
console.log(stringify(data, ['a', 'x']));
// '{"a":2,"x":[{"a":"3"},{},null,null,null,"foo",{},{},{},{},null]}'
console.log(stringify(data, ['a', 'x'], '--'));
// '{
//--"a": 2,
//--"x": [
//----{
//------"a": "3"
//----},
//----{},
//----null,
//----null,
//----null,
//----"foo",
//----{},
//----{},
//----{},
//----{},
//----null
//--]
//}'
以上是自己寫的實作stringify的全部代碼,如果代碼有不對的地方希望指出。
源碼
原文位址