共计 11259 个字符,预计需要花费 29 分钟才能阅读完成。
前言
JSON(JavaScript Object Notation)是一种语法,可用来序列化对象、数组、数值、字符串、布尔值和 null。它基于 JavaScript 语法,但与之不同:JavaScript 不是 JSON,JSON 也不是 JavaScript。
JSON 对象包含两个办法:parse 和 stringify 办法。除了这两个办法,JSON 这个对象自身并没有其余作用,也不能被调用或作为结构函数调用。
JSON.parse(text[, reviver])
办法阐明 :用来解析 JSON 字符串,结构由字符串形容的 JavaScript 值或对象。提供可选的 reviver 函数用以在返回之前对所失去的对象执行变换(操作)。
参数 :text,要被解析成 JavaScript 值的字符串;reviver(可选),转换器, 如果传入该参数(函数),能够用来批改解析生成的原始值,调用机会在 parse 函数返回之前。
返回值 :Object 类型, 对应给定 JSON 文本的对象 / 值。
异样:若传入的字符串不合乎 JSON 标准,则会抛出 SyntaxError 异样。
var json = '{"result":true,"count":42}'; | |
obj = JSON.parse(json); | |
console.log(obj.count); | |
// 42 | |
console.log(obj.result); | |
// true |
JSON.stringify(value[, replacer [, space]])
办法阐明 :将一个 JavaScript 值(对象或者数组) 转换为一个 JSON 字符串,如果指定了 replacer 是一个函数,则能够替换值,或者如果指定了 replacer 是一个数组,可选的仅包含指定的属性。
参数:
-
value
将要序列化成一个 JSON 字符串的值
- replacer 可选
- 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会通过该函数的转换和解决;
- 如果该参数是一个数组,则只有蕴含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
-
如果该参数为 null 或者未提供,则对象所有的属性都会被序列化;
-
space 可选
- 指定缩进用的空白字符串,用于丑化输入(pretty-print);
- 如果参数是个数字,它代表有多少的空格;下限为 10;
- 该值若小于 1,则意味着没有空格;
- 如果该参数为字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格;
- 如果该参数没有提供(或者为 null),将没有空格
-
返回值
一个示意给定值的 JSON 字符串
异样
- 当在循环援用时会抛出异样 TypeError (“cyclic object value”)(循环对象值)
- 当尝试去转换 BigInt 类型的值会抛出 TypeError (“BigInt value can’t be serialized in JSON”)(BigInt 值不能 JSON 序列化).
JSON.stringify()将值转换为相应的 JSON 格局:
- 转换值如果有 toJSON() 办法,该办法定义什么值将被序列化。
- 非数组对象的属性不能保障以特定的程序呈现在序列化后的字符串中。
- 布尔值、数字、字符串的包装对象在序列化过程中会主动转换成对应的原始值。
- undefined、任意的函数以及 symbol 值,在序列化过程中会 5. 被疏忽(呈现在非数组对象的属性值中时)或者被转换成 null(呈现在数组中时)。函数、undefined 被独自转换时,会返回 undefined,如 JSON.stringify(function(){}) or JSON.stringify(undefined).
- 对蕴含循环援用的对象(对象之间互相援用,造成有限循环)执行此办法,会抛出谬误。
- 所有以 symbol 为属性键的属性都会被齐全疏忽掉,即使 replacer 参数中强制指定蕴含了它们。
- Date 日期调用了 toJSON() 将其转换为了 string 字符串(同 Date.toISOString()),因而会被当做字符串解决。
- NaN 和 Infinity 格局的数值及 null 都会被当做 null。
- 其余类型的对象,包含 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
根本应用
1. 转换值如果有 toJSON() 办法,该办法定义什么值将被序列化
如果一个被序列化的对象领有 toJSON 办法,那么该 toJSON 办法就会笼罩该对象默认的序列化行为:不是该对象被序列化,而是调用 toJSON 办法后的返回值会被序列化,例如:
var obj = { | |
foo: 'foo', | |
toJSON: function () {return 'bar';} | |
}; | |
JSON.stringify(obj); // '"bar"' | |
JSON.stringify({x: obj}); // '{"x":"bar"}' |
2. 非数组对象的属性不能保障以特定的程序呈现在序列化后的字符串中
var obj = {a: function () {return "xxx";}, | |
b: 1, | |
c: undefined | |
}; | |
JSON.stringify(obj); // '{"b":1}' |
3. 布尔值、数字、字符串的包装对象在序列化过程中会主动转换成对应的原始值
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); | |
// '[1,"false",false]' |
4.undefined、函数、symbol 转化
作为对象属性值时,undefined、任意的函数以及 symbol 值,在序列化过程中会被疏忽
JSON.stringify({x: undefined, y: Object, z: Symbol(""), fn: function () {}}); | |
// '{}' |
作为数组元素值时,undefined、任意的函数以及 symbol 值,在序列化过程中会被转换成 null
JSON.stringify([undefined, Object, Symbol(""), function fn() {}]); | |
// '[null,null,null,null]' |
undefined、任意的函数以及 symbol 值独自转换时会返回 undefined
JSON.stringify(undefined); // undefined | |
JSON.stringify(Symbol("")); // undefined | |
JSON.stringify(function fn() {}); // undefined |
5. 对象之间互相援用,造成有限循环,会抛出谬误
var obj = {}; | |
obj.a = obj; | |
JSON.stringify(obj); |
利用这个可判断有对象里有无循环援用; 如:LeedCode 141. 环形链表
var hasCycle = function (head) { | |
try {JSON.stringify(head); | |
return false; | |
} catch (error) {return true;} | |
} |
6.symbol 为属性键的属性都会被齐全疏忽掉
JSON.stringify({[Symbol("foo")]: "foo"}); | |
// '{}' | |
JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]); | |
// '{}' | |
// 指定第二个参数 replacer | |
JSON.stringify({[Symbol.for("foo")]: "foo" }, function (k, v) {if (typeof k === "symbol") {return "a symbol";} | |
}); | |
// undefined | |
// toJSON | |
JSON.stringify({[Symbol("foo")]: "foo", | |
toJSON: function () {return "to JSON";}, | |
}); | |
// '"to JSON"' |
7.Date 日期调用了 toJSON() 将其转换为了 string 字符串(同 Date.toISOString()),因而会被当做字符串解决
var date = new Date(); | |
console.log(date.toISOString()); // 2019-04-03T14:50:20.573Z | |
console.log(date.toJSON()); // 2019-04-03T14:50:20.573Z | |
JSON.stringify(date); // '"2019-04-03T14:50:20.573Z"' | |
JSON.stringify({date: date}); // '{"date":"2019-04-03T14:50:20.573Z"}' |
8.NaN 和 Infinity 格局的数值及 null 都会被当做 null
JSON.stringify(NaN); // "null" | |
JSON.stringify(Infinity); // "null" | |
JSON.stringify(null); // "null" |
9. 其余类型的对象,包含 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
var map = new Map(); | |
map.set('a', 100); | |
var weakMap = new WeakMap(); | |
weakMap.set({a:1}, 100); | |
var set = new Set(); | |
set.add(100); | |
var weakSet = new WeakSet(); | |
weakSet.add({a: 100}); | |
JSON.stringify(map); // '{}' | |
JSON.stringify(weakMap); // '{}' | |
JSON.stringify(set); // '{}' | |
JSON.stringify(weakSet); // '{}' | |
// 不可枚举的属性默认会被疏忽:JSON.stringify( | |
Object.create( | |
null, | |
{x: { value: 'x', enumerable: false}, | |
y: {value: 'y', enumerable: true} | |
} | |
) | |
); | |
// "{"y":"y"}" |
指定 replacer 参数根本用法
1. replacer 作为函数时
JSON.stringify({name: 'zxx', sex: 'male', age: 18}, function (key, value) {if (typeof value === "string") {return undefined;} | |
return value; | |
}); | |
// '{"age":18}' |
2.replacer 为数组,数组的值代表将被序列化成 JSON 字符串的属性名
JSON.stringify({name: 'zxx', sex: 'male', age: 18}, ['name']); // '{"name":"zxx"}'
指定 space 参数
JSON.stringify({name: 'zxx', sex: 'male', age: 18}, null, 2); | |
/* | |
{ | |
"name": "zxx", | |
"sex": "male", | |
"age": 18 | |
} | |
*/ |
实现一个简略的 JSON.stringify
看源码前先理解以下前置知识点:
for in 循环
for…in 语句以任意程序迭代一个对象的除 Symbol 以外的可枚举属性,包含继承的可枚举属性
可枚举的数据,如:
- 对象
- 数组
-
字符串
可枚举数据是指对象自有属性的属性描述符 enumerable 为 true。
对于数组,循环返回的是数组的下标和数组的属性和原型上的办法和属性。let arr = [1, 2, 3]; for (let i in arr) {console.log(i, arr[i]); // 顺次输入 0 1, 1 2, 2 3 } let arr1 = []; Object.defineProperty(arr1, 0, { value : 1, writable : true, enumerable : false, // 可枚举性为 false configurable : true }); arr[1] = 2; for (let i in arr1) {console.log(i, arr1[i]); // 只输入 1 2 } 对于对象,循环返回的是对象的属性名和原型中的办法和属性
let obj = {name: 'zxx', age: 18, [Symbol('id')]: 1}; Object.prototype.say = function () {console.log('hello'); } for (let i in obj) {console.log(i); } // 输入 name age say 注:
for ... in
是为遍历对象属性而构建的,不倡议与数组一起应用(因为有稠密数组的存在,其实 JS 里的数组不肯定是程序构造存储的。当数组的键散布较为稠密,为了充沛节约空间,数组可能会进化为像对象一样的哈希表存储构造)- 遍历程序是对象属性的枚举程序
递归 + 栈 判断有无循环援用
如果之前看过 underscore 源码之比拟两个对象 ’ 相等 ’ 中解决循环援用 那么就很相熟了
- 查看循环后果
- 将对象增加到遍历对象的堆栈中(进栈)
- 递归
-
从遍历的对象堆栈中删除对象(出栈)
代码如下:function hasCycle (obj, property, stack) {var value = obj[property], classname = Object.prototype.toString.call(value); // 递归序列化对象和数组. if (typeof value == "object") { // 查看循环构造 for (length = stack.length; length--;) {if (stack[length] === value) { // 循环构造不能由“JSON.stringify”序列化 throw TypeError("Converting circular structure to JSON"); } } // 将对象增加到遍历对象的堆栈中 stack.push(value); if (classname === '[object Array]') { // 递归序列化数组元素 for (index = 0, length = value.length; index < length; index++) {hasCycle(value, index, stack) } } else {for (let key in value) {if (!(Object.prototype.toString.call(value[key]) === '[object Function]') && Object.prototype.hasOwnProperty.call(value, key)) {hasCycle(value, key, stack) } } } // 从遍历的对象堆栈中删除对象 stack.pop();} } 能够测试下:
let obj = {name: 'zxx'}; obj.attr = obj; let value = {}; value[""] = obj; hasCycle(value, "", []); // Uncaught TypeError: Converting circular structure to JSON let arr = [1]; arr[1] = arr; let value1 = {}; value1[""] = obj; hasCycle(value1, "", []); // Uncaught TypeError: Converting circular structure to JSON
上面来看 json3.js 中精简代码:
var objectProto = Object.prototype, | |
getClass = objectProto.toString, | |
isProperty = objectProto.hasOwnProperty, | |
objectTypes = { | |
function: true, | |
object: true, | |
}; | |
var functionClass = "[object Function]", | |
dateClass = "[object Date]", | |
numberClass = "[object Number]", | |
stringClass = "[object String]", | |
arrayClass = "[object Array]", | |
booleanClass = "[object Boolean]"; | |
var simpleStringify = function (source, filter, width) { | |
var whitespace, callback, properties, className; | |
// 排除 null | |
if (objectTypes[typeof filter] && filter) {className = getClass.call(filter); | |
if (className == functionClass) {callback = filter;} else if (className == arrayClass) {properties = {}; | |
for (var index = 0, length = filter.length, value; index < length;) {value = filter[index++]; | |
className = getClass.call(value); | |
if (className == "[object String]" || className == "[object Number]") {properties[value] = 1; | |
} | |
} | |
} | |
} | |
if (width) {className = getClass.call(width); | |
if (className == numberClass) { | |
// 将 'width' 转换为整数 | |
if ((width -= width % 1) > 0) {if (width > 10) {width = 10;} | |
for (whitespace = ""; whitespace.length < width;) {whitespace += " ";} | |
} | |
} else if (className == stringClass) {whitespace = width.length <= 10 ? width : width.slice(0, 10); | |
} | |
} | |
return serialize( | |
"", | |
((value = {}), (value[""] = source), value), | |
callback, | |
properties, | |
whitespace, | |
"", | |
[]); | |
}; | |
var forOwn = function (object, callback) {var isFunction = getClass.call(object) == functionClass, | |
property; | |
// for...in 语句以任意程序迭代一个对象的除 Symbol 以外的可枚举属性,包含继承的可枚举属性 | |
for (property in object) {if (!isFunction && isProperty.call(object, property)) {callback(property); | |
} | |
} | |
}; | |
var quote = function (value) {return '"'+ value +'"';}; | |
var serialize = function ( | |
property, | |
object, | |
callback, | |
properties, | |
whitespace, | |
indentation, | |
stack | |
) {var value = object[property], | |
type, | |
className, | |
results, | |
element, | |
index, | |
length, | |
prefix, | |
result; | |
if (typeof value == "object" && value) {// 转换值如果有 toJSON() 办法,调用 | |
// 解决 Date | |
if ( | |
value.getUTCFullYear && | |
getClass.call(value) == dateClass && | |
value.toJSON === Date.prototype.toJSON | |
) {value = value.toJSON(); | |
} else if (typeof value.toJSON == "function") {value = value.toJSON(property); | |
} | |
} | |
if (callback) { | |
// 如果提供了替换函数,则调用它以获取值用于序列化 | |
value = callback.call(object, property, value); | |
} | |
// 如果 value 是 `undefined` or `null` 就间接返回 | |
if (value == undefined) {return value === undefined ? value : "null";} | |
type = typeof value; | |
if (type == "object") {className = getClass.call(value); | |
} | |
switch (className || type) { | |
case "boolean": | |
case booleanClass: | |
return "" + value; | |
case "number": | |
case numberClass: | |
// `Infinity'和 NaN' 序列化为 `“空”`。return value > -1 / 0 && value < 1 / 0 ? ""+ value :"null"; | |
case "string": | |
case stringClass: | |
return quote("" + value); | |
} | |
// 递归序列化对象和数组. | |
if (typeof value == "object") { | |
// 查看循环构造 | |
for (length = stack.length; length--;) {if (stack[length] === value) { | |
// 循环构造不能由“JSON.stringify”序列化 | |
throw TypeError("Converting circular structure to JSON"); | |
} | |
} | |
// 将对象增加到遍历对象的堆栈中 | |
stack.push(value); | |
results = []; | |
// 缩进 | |
prefix = indentation; | |
indentation += whitespace; | |
if (className == arrayClass) { | |
// 递归序列化数组元素 | |
for (index = 0, length = value.length; index < length; index++) { | |
element = serialize( | |
index, | |
value, | |
callback, | |
properties, | |
whitespace, | |
indentation, | |
stack | |
); | |
results.push(element === undefined ? "null" : element); | |
} | |
result = results.length | |
? whitespace | |
? "[\n" + | |
indentation + | |
results.join(",\n" + indentation) + | |
"\n" + | |
prefix + | |
"]" | |
: "[" + results.join(",") + "]" | |
: "[]";} else { | |
// 递归序列化对象成员 | |
forOwn(properties || value, function (property) { | |
var element = serialize( | |
property, | |
value, | |
callback, | |
properties, | |
whitespace, | |
indentation, | |
stack | |
); | |
if (element !== undefined) { | |
results.push(quote(property) + ":" + (whitespace ? "" :"") + element | |
); | |
} | |
}); | |
result = results.length | |
? whitespace | |
? "{\n" + | |
indentation + | |
results.join(",\n" + indentation) + | |
"\n" + | |
prefix + | |
"}" | |
: "{" + results.join(",") + "}" | |
: "{}";} | |
// 从遍历的对象堆栈中删除对象 | |
stack.pop(); | |
return result; | |
} | |
}; |
测试:
var obj = { | |
foo: 'foo', | |
toJSON: function () {return 'bar';} | |
}; | |
simpleStringify(obj); // '"bar"' | |
simpleStringify({x: obj}); // '{"x":"bar"}' | |
var obj1 = {a: function () {return 'xxx'}, | |
b: 1, | |
c: undefined | |
} | |
simpleStringify(obj1); // '{"b":1}' | |
simpleStringify({x: undefined, y: Object, z: Symbol(""), fn: function () {}}); | |
// '{}' | |
simpleStringify([undefined, Object, Symbol(""), function fn() {}]); | |
// '[null,null,null,null]' | |
simpleStringify(undefined); // undefined | |
simpleStringify(Symbol("")); // undefined | |
simpleStringify(function fn() {}); // undefined | |
var circularObj = {}; | |
circularObj.a = circularObj; | |
simpleStringify(circularObj); | |
// 报错 Uncaught TypeError: Converting circular structure to JSON | |
simpleStringify({[Symbol("foo")]: "foo"}); | |
// '{}' | |
simpleStringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]); | |
// '{}' | |
// 指定第二个参数 replacer | |
simpleStringify({[Symbol.for("foo")]: "foo"}, | |
function (k, v) {if (typeof k === "symbol") {return "a symbol";} | |
} | |
); | |
// undefined | |
// 指定第三个参数 | |
simpleStringify({name: 'xman', age:18}, null, 2); | |
// '{\n"name":"xman",\n"age": 18\n}' | |
simpleStringify({[Symbol("foo")]: "foo", | |
toJSON: function () {return "to JSON";}, | |
}); | |
// '"to JSON"' | |
simpleStringify(new Date()); // '"2022-06-01T09:18:54.216Z"' | |
simpleStringify(NaN); // "null" | |
simpleStringify(Infinity); // "null" | |
simpleStringify(null); // "null" | |
var map = new Map(); | |
map.set('a', 100); | |
var weakMap = new WeakMap(); | |
weakMap.set({a:1}, 100); | |
var set = new Set(); | |
set.add(100); | |
var weakSet = new WeakSet(); | |
weakSet.add({a: 100}); | |
simpleStringify(map); // '{}' | |
simpleStringify(weakMap); // '{}' | |
simpleStringify(set); // '{}' | |
simpleStringify(weakSet); // '{}' | |
// 不可枚举的属性默认会被疏忽:simpleStringify( | |
Object.create(null, {x: { value: "x", enumerable: false}, | |
y: {value: "y", enumerable: true}, | |
}) | |
); | |
// "{"y":"y"}" | |
参考资料
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
https://bestiejs.github.io/json3