Open FrankKai opened 4 years ago
就我目前3年(实习了1年,965了1年,996了2年,算3年感觉少了,说是4年老司机也不为过吧。)的工作经验来看,JSON.stringify一般有以下用途:
但其实除了上面两种常用的用法之外,JSON.stringify还有许多更加强大的特性,值得系统性学习。 关键是这些特性可能是你开发过程中经常用到的,只是你不知道而已。
也许你觉得这是一篇枯燥的长篇大论的讲用法的博文,那么我就说几个有趣的场景吧:
如果这几个问题看了之后是一脸懵逼,那么一定要细细阅读一下这篇博文,答案就在文中。聪明的你一定会找到答案的。
const obj = { foo: 'hi', bar: { name: 'bar', age: 3 }, baz: ['hello','javascript'] } const arr = ['hi', { name: 'bar', age: 3 }, ['hello','javascript']]; const deepCopy = JSON.parse(JSON.stringify(obj/arr))
深拷贝之后,deepCopy会生成一个内存独立的obj或者arr。 也就是说obj/arr与deepCopy存储在不同的堆内存,修改obj/arr不会影响deepCopy,修改deepCopy也不会影响obj或者arr。
若是对于深浅拷贝不理解,建议先找资料系统性学习一下。
服务端存储重依赖前端的数据:例如fabric.js的canvas模板数据,vue-amap的svg路径信息等等。 localStorage/sessionStorage存储的数据: LocalStorage/SessionStorage The keys and the values are always strings。
例如Canvas,SVG信息,服务端做持久化。
const complexFabricJSCanvasTemplate = { ... }; const complexVueAMapSVGObject = { ... }; const JSONStr = JSON.stringify(complexFabricJSCanvasTemplate/complexVueAMapSVGObject); axios.post('/create', { name: 'fabric.js', // "vue-amap" data: JSONStr, }) .then((res)=>{ console.log(res); }) .catch((err)=>{ console.log(err); });
const testObj = {foo: 1, bar: 'hi', baz: { name: 'frankkai', age: 25 }} localStorage.setItem('testObj', JSON.stringify(testObj ));
若不转化,也不会报错,会导致存储失效:localStorage.getItem('testObj');// "[object Object]"
localStorage.getItem('testObj');// "[object Object]"
从服务端接口查询到存储好的Canvas或SVG数据,做解析后传入到fabric.js,vue-amap等进行绘制。
return new Promise((resolve)=>{ axios.get('/retrive', { name: 'fabric.js', // "vue-amap" }) .then((res)=>{ const complexFabricJSCanvasTemplate = JSON.parse(res.result); const complexVueAMapSVGObject = JSON.parse(res.result); resolve(complexFabricJSCanvasTemplate/complexVueAMapSVGObject); }) .catch((err)=>{ console.log(err); }); })
let arr = []; let lenCode = `${JSON.stringify(arr)}.length` // '[].length'
假如不使用JSON.stringify,会返回'.length'。
let arr = []; let lenCode = `${arr}.length` // '.length'
再来看一个模拟可选链的例子:
可选链的执行过程。如果不能有左侧变量赋值,如何模拟?
let data = [{name: 'foo'}, {name: 'bar'}]; data?.map((item) => item?.name)
=>
let data = [{name: 'foo'}, {name: 'bar'}]; function optionalChain(src, operation) { let temp = src; if(temp === null || temp === undefined){ return undefined; } return eval(`${JSON.stringify(temp)}${operation}`); } optionalChain(data, '.map((item) => item?.name)');
注意:假如直接使用eval(${temp}${operation}),由于temp是数组,会执行[].toString() => "",导致标识数组的代码丢失。使用JSON.stringify序列化之后,就没有任何问题了,会生成代码字符串"[].map((item) => item?.name)"
${temp}${operation}
console.log(JSON.stringify({ x: 5, y: 6 })); // expected output: "{"x":5,"y":6}" console.log(JSON.stringify([new Number(3), new String('false'), new Boolean(false)])); // expected output: "[3,"false",false]" console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] })); // expected output: "{"x":[10,null,null,null]}" console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5))); // expected output: ""2006-01-02T15:04:05.000Z""
var foo = {foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7}; // replacer为数组 JSON.stringify(foo, ['week', 'month']); // '{"week":45,"month":7}', 只返回week"和"month" // replacer为函数 function replacer(key, value) { if (typeof value === 'string') return undefined; return value; } JSON.stringify(foo, replacer); // '{"week":45,"month":7}',返回callback不为undefined的。
首行缩进两个空格。
JSON.stringify({ a: 2 }, null, ' '); // JSON.stringify({a:2}, null, 2)
"{ "a": 2 }"
JSON.stringify(value, [ ,replacer[ ,space]]);
转化为JSON string的值。
过滤数据。
函数:replacer可以是函数,返回undefined时不输出数据,非undefined的数据被输出。 字符串数组:replacer可以是数组,一般是string,也可以是number。指定输出JSON的属性白名单。['week', 'month']。 null或不写:replacer为null或者不写时,所有属性都被输出。null的话一般用于不过滤数据仅设置space的情况。
['week', 'month']
疑惑:replacer的数组中是数字?[1,2,3,4,5] ?
const arr = { 999: 'hi', foo: 'js', bar:' java' }; JSON.stringify(arr, [999, 'foo']); // 打印出"{"999":"hi","foo":"js"}"。 这里的999是number类型。
增强可读性的缩进空格或填充字符串。
JSON string。
序列化自引用的对象时会报这个错。高德地图的segments就是自引用对象。如何序列化自引用的对象可见下文。
包含突破Number存储上线的BitInt类型的obj,不能被序列化。
JSON.stringify({foo: 1n}) // TypeError “BigInt value can't be serialized in JSON”
如果想更好的使用JSON.stringify(),下面这些使用须知一定要掌握。
JSON.stringify({ foo: Symbol('foo') });// "{}"
JSON.stringify({ [Symbol('foo')]: 'foo' });// '{}' JSON.stringify({ [Symbol.for('foo')]: 'foo' }, [Symbol.for('foo')]);// '{}'
var foo = [['foo',1]];var mp = new Map(foo);JSON.stringify(mp); // "{}"
let a = ['foo', 'bar']; a['baz'] = 'quux'; // a: [ 0: 'foo', 1: 'bar', baz: 'quux' ]; JSON.stringify(a); // '["foo","bar"]'
JSON.stringify([new Float32Array([1]), new Float64Array([1])]);// '[{"0":1},{"0":1}]'
究极使用示例:
JSON.stringify({}); // '{}' JSON.stringify(true); // 'true' JSON.stringify('foo'); // '"foo"' JSON.stringify([1, 'false', false]); // '[1,"false",false]' JSON.stringify([NaN, null, Infinity]); // '[null,null,null]' JSON.stringify({ x: 5 }); // '{"x":5}' JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)) // '"2006-01-02T15:04:05.000Z"' JSON.stringify({ x: 5, y: 6 }); // '{"x":5,"y":6}' JSON.stringify([new Number(3), new String('false'), new Boolean(false)]); // '[3,"false",false]' // String-keyed array elements are not enumerable and make no sense in JSON let a = ['foo', 'bar']; a['baz'] = 'quux'; // a: [ 0: 'foo', 1: 'bar', baz: 'quux' ] JSON.stringify(a); // '["foo","bar"]' JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] }); // '{"x":[10,null,null,null]}' // Standard data structures JSON.stringify([new Set([1]), new Map([[1, 2]]), new WeakSet([{a: 1}]), new WeakMap([[{a: 1}, 2]])]); // '[{},{},{},{}]' // TypedArray JSON.stringify([new Int8Array([1]), new Int16Array([1]), new Int32Array([1])]); // '[{"0":1},{"0":1},{"0":1}]' JSON.stringify([new Uint8Array([1]), new Uint8ClampedArray([1]), new Uint16Array([1]), new Uint32Array([1])]); // '[{"0":1},{"0":1},{"0":1},{"0":1}]' JSON.stringify([new Float32Array([1]), new Float64Array([1])]); // '[{"0":1},{"0":1}]' // toJSON() JSON.stringify({ x: 5, y: 6, toJSON(){ return this.x + this.y; } }); // '11' // Symbols: JSON.stringify({ x: undefined, y: Object, z: Symbol('') }); // '{}' JSON.stringify({ [Symbol('foo')]: 'foo' }); // '{}' JSON.stringify({ [Symbol.for('foo')]: 'foo' }, [Symbol.for('foo')]); // '{}' JSON.stringify({ [Symbol.for('foo')]: 'foo' }, function(k, v) { if (typeof k === 'symbol') { return 'a symbol'; } }); // undefined // Non-enumerable properties: JSON.stringify( Object.create(null, { x: { value: 'x', enumerable: false }, y: { value: 'y', enumerable: true } }) ); // '{"y":"y"}' // BigInt values throw JSON.stringify({x: 2n}); // TypeError: BigInt value can't be serialized in JSON
replacer可以是function,也可以是array。
function replacer(key="", value) { return value; }
作为function,有两个参数key 和 value。 默认情况下replacer函数的key为“”,对于对象中的每个值,都会call一次这个函数。
`function replacer(key,value){ return value; } JSON.stringify({foo: false,bar:123},replacer);`
返回值有以下几种情况:
return 123; // "123"
return "foo"; // "foo"
return true; // "true"
return null; //"null"
return value;"{"foo":false,"bar":123}"
JSON.stringify({foo: false,bar:undefined},replacer);"{"foo":false}"。
使用强大的undefined是需要注意:
undefined 用过axios的同学这里破案了:当我们的post请求的reqBody包含以undefined为值的参数时,根本不会发到服务端的接口,而null可以。这就是JSON.stringify()的功劳。 v = JSON.stringify(v)时,会将undefined类型的值自动过滤掉。
JSON.stringify()
v = JSON.stringify(v)
// axios源码 buildURL.js 37~56行: utils.forEach(params, function serialize(val, key) { if (utils.isArray(val)) { key = key + '[]'; } else { val = [val]; } utils.forEach(val, function parseValue(v) { if (utils.isDate(v)) { v = v.toISOString(); } else if (utils.isObject(v)) { v = JSON.stringify(v); // 注意这里 } parts.push(encode(key) + '=' + encode(v)); }); });
function replacer(key,value){ if(typeof value ==='boolean'){return undefined} return value; } JSON.stringify([1, 'false', false],replacer); // "[1,"false",null]"
function replacer(key, value) { // 返回undefined过滤属性 if (typeof value === 'string') { return undefined; } return value; }
var foo = {foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7}; JSON.stringify(foo, replacer); // '{"week":45,"month":7}'
```js JSON.stringify(foo, ['week', 'month']); // '{"week":45,"month":7}',
JSON.stringify({ a: 2 }, null, ' '); // '{ // "a": 2 // JSON.stringify({ uno: 1, dos: 2 }, null, '\t'); // returns the string: // '{ // "uno": 1, // "dos": 2 // }'
如果要被字符串化的对象具有一个名为toJSON的属性,其值是一个函数,则该toJSON()方法将自定义JSON字符串化行为:代替被序列化的对象,该toJSON()方法返回的值将被序列化,而不是被序列化的对象。JSON.stringify()调用toJSON一个参数:
var obj = { data: 'data', toJSON (key) { return key; } }; JSON.stringify(obj); // '"""" JSON.stringify({ obj }) // '{"obj":"'obj'"}' JSON.stringify([ obj ]) // '["'0'"]'
TypeError: Converting circular structure to JSON
const circularReference = {}; circularReference.myself = circularReference; // Serializing circular references throws "TypeError: cyclic object value" JSON.stringify(circularReference);
路径规划plans的路径SVG信息segments对象。
需求是这样的,前端需要将路线的信息传递给后端,其中包括经纬度数组和SVG数组。 用JSON.stringify()序列化经纬度数组是ok的,但是序列化的SVG数组是自引用的,会报错。(是后来才知道这个SVG数组可以不往服务端存的,不过当时序列化报错是真的懵逼了)
使用Douglas Crockford的cycle.js
它在全局JSON对象上新增了2个方法:
myself:obj
myself:{$ref: "$"}
使用示例:
var circularReference = {}; circularReference.myself = circularReference; JSON.decycle(circularReference); // { "$ref": "$" }
不相等。
var a = JSON.stringify({ foo: "bar", baz: "quux" }) //'{"foo":"bar","baz":"quux"}' var b = JSON.stringify({ baz: "quux", foo: "bar" }) //'{"baz":"quux","foo":"bar"}' console.log(a === b) // false
localStorage只能存储string类型的数据,因此需要使用JSON.stringify()将对象序列化为JSON string。
var session = { 'screens': [], 'state': true }; session.screens.push({ 'name': 'screenA', 'width': 450, 'height': 250 }); session.screens.push({ 'name': 'screenB', 'width': 650, 'height': 350 }); session.screens.push({ 'name': 'screenC', 'width': 750, 'height': 120 }); localStorage.setItem('session', JSON.stringify(session)); var restoredSession = JSON.parse(localStorage.getItem('session')); console.log(restoredSession);
值为undefined? replacer对它做过滤了? toJSON方法中做过滤了? 属性的enumerable值为false? Symbol? Map?Set?WeakMap?WeakSet? 找原因吧。
v = JSON.stringify(v); // v = {foo: undefined} =>"{}"
源码在buildURL.js 37~56行
const testObj = {foo: 1, bar: 'hi', baz: { name: 'frankkai', age: 25 }} JSON.stringify(testObj, null, 4);
"{ "foo": 1, "bar": "hi", "baz": { "name": "frankkai", "age": 25 } }"
这是因为自引用之后,会限制死循环。 引擎应该是做了特殊的处理,发现这种无限循环时自动抛出异常。 这种对象不能序列化了吗?可以使用cycle.js的decycle(序列化。
参考资料:
就我目前3年(实习了1年,965了1年,996了2年,算3年感觉少了,说是4年老司机也不为过吧。)的工作经验来看,JSON.stringify一般有以下用途:
但其实除了上面两种常用的用法之外,JSON.stringify还有许多更加强大的特性,值得系统性学习。 关键是这些特性可能是你开发过程中经常用到的,只是你不知道而已。
也许你觉得这是一篇枯燥的长篇大论的讲用法的博文,那么我就说几个有趣的场景吧:
如果这几个问题看了之后是一脸懵逼,那么一定要细细阅读一下这篇博文,答案就在文中。聪明的你一定会找到答案的。
工作中常用JSON.stringify()
深拷贝:深拷贝引用类型的数据(JSON.parse(JSON.stringify(obj/arr)))
深拷贝之后,deepCopy会生成一个内存独立的obj或者arr。 也就是说obj/arr与deepCopy存储在不同的堆内存,修改obj/arr不会影响deepCopy,修改deepCopy也不会影响obj或者arr。
若是对于深浅拷贝不理解,建议先找资料系统性学习一下。
序列化:服务端存储重依赖前端的数据,localStorage/sessionStorage存储的数据
服务端存储重依赖前端的数据:例如fabric.js的canvas模板数据,vue-amap的svg路径信息等等。 localStorage/sessionStorage存储的数据: LocalStorage/SessionStorage The keys and the values are always strings。
存储重前端功能的数据
例如Canvas,SVG信息,服务端做持久化。
localStorage/sessionStorage存储的数据
若不转化,也不会报错,会导致存储失效:
localStorage.getItem('testObj');// "[object Object]"
查询JSON数据做解析
从服务端接口查询到存储好的Canvas或SVG数据,做解析后传入到fabric.js,vue-amap等进行绘制。
拼接代码
假如不使用JSON.stringify,会返回'.length'。
再来看一个模拟可选链的例子:
可选链的执行过程。如果不能有左侧变量赋值,如何模拟?
=>
注意:假如直接使用eval(
${temp}${operation}
),由于temp是数组,会执行[].toString() => "",导致标识数组的代码丢失。使用JSON.stringify序列化之后,就没有任何问题了,会生成代码字符串"[].map((item) => item?.name)"初识JSON.stringify()
序列化数据
过滤数据
格式化数据
首行缩进两个空格。
=>
JSON.stringify(value, [ ,replacer[ ,space]]);语法
参数
value
转化为JSON string的值。
replacer
过滤数据。
函数:replacer可以是函数,返回undefined时不输出数据,非undefined的数据被输出。 字符串数组:replacer可以是数组,一般是string,也可以是number。指定输出JSON的属性白名单。
['week', 'month']
。 null或不写:replacer为null或者不写时,所有属性都被输出。null的话一般用于不过滤数据仅设置space的情况。疑惑:replacer的数组中是数字?[1,2,3,4,5] ?
space
增强可读性的缩进空格或填充字符串。
返回值
JSON string。
异常
TypeError “cyclic object value”
序列化自引用的对象时会报这个错。高德地图的segments就是自引用对象。如何序列化自引用的对象可见下文。
TypeError “BigInt value can't be serialized in JSON”
包含突破Number存储上线的BitInt类型的obj,不能被序列化。
JSON.stringify()描述
常见使用须知
如果想更好的使用JSON.stringify(),下面这些使用须知一定要掌握。
JSON.stringify({ foo: Symbol('foo') });// "{}"
JSON.stringify({ [Symbol('foo')]: 'foo' });// '{}' JSON.stringify({ [Symbol.for('foo')]: 'foo' }, [Symbol.for('foo')]);// '{}'
var foo = [['foo',1]];var mp = new Map(foo);JSON.stringify(mp); // "{}"
let a = ['foo', 'bar']; a['baz'] = 'quux'; // a: [ 0: 'foo', 1: 'bar', baz: 'quux' ]; JSON.stringify(a); // '["foo","bar"]'
JSON.stringify([new Float32Array([1]), new Float64Array([1])]);// '[{"0":1},{"0":1}]'
究极使用示例:
replacer 参数
replacer可以是function,也可以是array。
function
作为function,有两个参数key 和 value。 默认情况下replacer函数的key为“”,对于对象中的每个值,都会call一次这个函数。
返回值有以下几种情况:
return 123; // "123"
return "foo"; // "foo"
return true; // "true"
return null; //"null"
return value;"{"foo":false,"bar":123}"
JSON.stringify({foo: false,bar:undefined},replacer);"{"foo":false}"。
使用强大的undefined是需要注意:
undefined 用过axios的同学这里破案了:当我们的post请求的reqBody包含以undefined为值的参数时,根本不会发到服务端的接口,而null可以。这就是
JSON.stringify()
的功劳。v = JSON.stringify(v)
时,会将undefined类型的值自动过滤掉。常用方式
var foo = {foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7}; JSON.stringify(foo, replacer); // '{"week":45,"month":7}'
space 参数
toJSON()的表现
如果要被字符串化的对象具有一个名为toJSON的属性,其值是一个函数,则该toJSON()方法将自定义JSON字符串化行为:代替被序列化的对象,该toJSON()方法返回的值将被序列化,而不是被序列化的对象。JSON.stringify()调用toJSON一个参数:
JSON.stringify()序列化循环引用时的问题
自引用对象抛出TypeError
TypeError: Converting circular structure to JSON
JSON.stringify高德地图vue-amap中的自引用对象
路径规划plans的路径SVG信息segments对象。
需求是这样的,前端需要将路线的信息传递给后端,其中包括经纬度数组和SVG数组。 用JSON.stringify()序列化经纬度数组是ok的,但是序列化的SVG数组是自引用的,会报错。(是后来才知道这个SVG数组可以不往服务端存的,不过当时序列化报错是真的懵逼了)
如何序列化自引用对象?
使用Douglas Crockford的cycle.js
它在全局JSON对象上新增了2个方法:
myself:obj
替换为myself:{$ref: "$"}
)使用示例:
JSON.stringify()序列化具有相同属性相同值但属性顺序不同的对象,结果相等吗?
不相等。
JOSN.stringify()与localStorage的使用示例
localStorage只能存储string类型的数据,因此需要使用JSON.stringify()将对象序列化为JSON string。
回答一下文章开头的问题
为什么这个对象明明有foo属性,序列化后在数据库持久化存储之后,这个属性咋就没了呢?
值为undefined? replacer对它做过滤了? toJSON方法中做过滤了? 属性的enumerable值为false? Symbol? Map?Set?WeakMap?WeakSet? 找原因吧。
为什么axios发送post请求时,如果req的body包含undefined值的参数,在发给服务端的请求中会消失?
源码在buildURL.js 37~56行
打印出的JSON字符串就这么不易读么?出了store成一个variable然后parse之外,能不能直接parse就清晰看到它的数据结构呢?
=>
为什么序列化一个对象,它还报错呢?报的还是那种自己引用自己的错误?这种对象不能序列化了吗?
这是因为自引用之后,会限制死循环。 引擎应该是做了特殊的处理,发现这种无限循环时自动抛出异常。 这种对象不能序列化了吗?可以使用cycle.js的decycle(序列化。
参考资料: