【JS】详谈JSON与JavaScript
JSON 在编程生涯简直就是无处不在啊。
- 那么 JSON 是什么呢?
- 跟我们的 JavaScript 有什么关系呢?
- 在 JavaScript 中,我们如何处理 JSON 数据呢?
一、JSON
JSON(JavaScript Object Natation)是一种轻量级的数据交换格式。由于易于阅读、编写,以及便于机器解析与生成的特性,相比 XML,它更小、更快、更易解析,使得它成为理想的数据交换语言。完全独立于语言的一种文本格式。
例如用以下 JSON 数据来描述一个人的信息:
{"name": "Frankie",
"age": 20,
"skills": ["Java", "JavaScript", "TypeScript"]
}
注意,JavaScript 不是 JSON,JSON 也不是 JavaScript。但 JSON 与 JavaScript 是存在渊源的,JSON 的数据格式是从 JavaScript 对象中演变出来的。(从名称上可以体现)
二、JSON 与 JavaScript 的区别
JSON 是一种数据格式,也可以说是一种规范。JSON 是用于跨平台数据交流的,独立于语言和平台。而 JavaScript 对象是一个实例,存在于内存中。JavaScript 对象是没办法传输的,只有在被序列化为 JSON 字符串后才能传输。
JavaScript 类型 | JSON 的不同点 |
---|---|
对象和数组 | 属性名称必须是双引号括起来的字符串;最后一个属性后不能有逗号 |
数值 | 禁止出现前导零( JSON.stringify() 方法自动忽略前导零,而在 JSON.parse() 方法中将会抛出 SyntaxError);如果有小数点,则后面至少跟着一位数字。 |
字符串 | 只有有限的一些字符可能会被转义;禁止某些控制字符; Unicode 行分隔符 (U+2028)和段分隔符 (U+2029)被允许 ; 字符串必须用双引号括起来。请参考下面的示例,可以看到 JSON.parse() 能够正常解析,但将其当作 JavaScript 解析时会抛出 SyntaxError 错误: |
let code = '"\u2028\u2029"'JSON.parse(code) // 正常
eval(code) // 错误
在 JavaScript 中,我们不能把以下对象叫做 JSON,如:
// 这只是 JS 对象var people = {}
// 这跟 JSON 就更不沾边了,只是 JS 的对象
var people = { name: 'Frankie', age: 20 }
// 这跟 JSON 就更不沾边了,只是 JS 的对象
var people = { 'name': 'Frankie', 'age': 20 }
// 我们可以把这个称做:JSON 格式的 JS 对象
var people = { "name": "Frankie", "age": 20 }
// 我们可以把这个称做:JSON 格式的字符串
var people = '{"name":"Frankie","age":20}'
// 稍复杂的 JSON 格式的数组
var peopleArr = [
{ "name": "Frankie", "age": 20 },
{ "name": "Mandy", "age": 18 }
]
// 稍复杂的 JSON 格式字符串
var peopleStr = '[{"name":"Frankie","age":20},{"name":"Mandy","age":18}]'
在 JavaScript 中,JSON
对象包含两个方法,用于解析的 JSON.parse()
和转换的 JSON.stringify()
方法。除了这两个方法,JSON
这个对象本身并没有其他作用,也不能被调用或者作为构造函数调用。
三、JSON.stringify()
将一个 JavaScript 对象或值转换为 JSON 字符串。
- 参数
value
,是将要序列化成 JSON 字符串的值。 - 参数
replacer
(可选),如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数未提供(或者值为null
),则对象所有的属性都会被序列化。
const people = {name: 'Frankie',
age: 20
}
const peopleStr1 = JSON.stringify(people, ['name'])
const peopleStr2 = JSON.stringify(people, (key, value) => {
if (typeof value === 'string') {
return undefined
}
return value
})
console.log(peopleStr1) // '{"name":"Frankie"}'
console.log(peopleStr2) // '{"age":20}'
- 参数
space
(可选),指定缩进用的空白字符串,用于美化输出(pretty-print)。如果参数为数字,它表示有多少个空格,值大于 10 时,输出空格为 10,小于 1 则表示没有空格。如果参数为字符串,该字符串将被将被作为空格。如果参数没有提供(或者值为null
),将没有空格。注意,若使用非空字符串作为参数值,就不能被JSON.parse()
解析了,会抛出SyntaxError
错误。
看示例:
const symbol = Symbol()const func = () => { }
const people = {
name: 'Frankie',
age: 20,
birthday: new Date(),
sex: undefined,
home: null,
say: func,
[symbol]: 'This is Symbol',
skills: ['', undefined, , 'JavaScript', undefined, symbol, func],
course: {
name: 'English',
score: 90
},
prop1: NaN,
prop2: Infinity,
prop3: new Boolean(true) // or new String('abc') or new Number(10)
}
const replacer = (key, value) => {
// 这里我其实没做什么处理,跟忽略 replacer 参数是一致的。
// 若符合某种条件不被序列化,return undefined 即可。
// 比如 if (typeof value === 'string') return undefined
// 也可以通过该函数来看看序列化的执行顺序。
// console.log('key: ', key)
// console.log('value: ', value)
return value
}
// 序列化操作
const peopleStr = JSON.stringify(people, replacer)
// '{"name":"Frankie","age":20,"birthday":"2021-01-17T10:24:39.333Z","home":null,"skills":["",null,null,"JavaScript",null,null,null],"course":{"name":"English","score":90},"prop1":null,"prop2":null,"prop3":true}'
console.log(peopleStr)
console.log(JSON.stringify(function(){})) // undefined
console.log(JSON.stringify(undefined)) // undefined
结合以上示例,有以下特点:
非数组对象的属性不能保证以特定的顺序属性出现在序列化后的字符串中。(示例可能没体现出来)
- 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。(如
prop3
) undefined
、任意的函数以及symbol
值,在序列化过程中有两种不同的情况。若出现在非数组对象的属性值中,会被忽略;若出现在数组中,会被转换成null
。- 函数、
undefined
被单独转换时,会返回undefined
。 - 所有以 symbol 为属性值的属性都会被完全忽略掉,即便
replacer
参数中强制指定包含了它们。 - Date 日期调用了其内置的
toJSON()
方法将其转换成字符串(同Date.toISOString()
),因此会被当做字符串处理。 NaN
和Infinity
格式的数值及null
都会被当做null
。- 其他类型的对象,包括
Map
、Set
、WeakMap
、WeakSet
,仅会序列化可枚举的属性。 - 转换值如果含有
toJSON()
方法,该方法定义什么值将被序列化。 - 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
针对最后两点举例说明:
- 若对象本身实现了
toJSON()
方法,那么调用JSON.stringify()
方法时,JSON.stringify()
会将这个对象的toJSON()
方法的返回值作为参数去进行序列化。
const people = {name: 'Frankie',
age: 20,
toJSON: () => {
return { name: 'Mandy' }
}
}
console.log(JSON.stringify(people))
// 结果是 {"name":"Mandy"},而不是 {"name":"Frankie","age":20}
// 需要注意的是,若对象的 toJSON 属性值不是函数的话,仍然是该对象作为参数进行序列化。
// 上面还提到 Date 对象本身内置了 toJSON() 方法,所以以下返回结果是:
// "2021-01-17T09:40:08.302Z"
console.log(JSON.stringify(new Date()))
// 假如我去修改 Date 原型上的 toJSON 方法,结果会怎样呢?
Date.prototype.toJSON = function () { return '被改写了' }
console.log(JSON.stringify(new Date())) // "被改写了"
- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
const foo = {}const bar = {
b: foo
}
foo.a = bar
console.log(foo)
// 如果这时候对 foo 进行序列化操作,就会抛出错误。
JSON.stringify(foo) // Uncaught TypeError: Converting circular structure to JSON
foo 对象和 bar 对象会无限相互引用,可以看下 foo 打印结果如下,如果此时对 foo 进行序列化操作,就会抛出错误:Uncaught TypeError: Converting circular structure to JSON
。
针对这个问题,看看别人的解决方法,看这里 JSON-js。具体用法是,先引入其中的 cycle.js
脚本,然后 JSON.stringify(JSON.decycle(foo))
就 OK 了。
JSON.stringify() 总结:
- 若被序列化的对象,存在
toJSON()
方法,真正被序列化的其实是toJSON()
方法的返回值。 - 若提供了
replacer
参数,应用这个函数过滤器,传入的函数过滤器的值是第 1 步返回的值。 - 对第 2 步返回的每个值,进行相应的序列化。
- 如果提供了
space
参数,执行相应的格式化操作。
四、JSON.parse()
JSON.parse()
方法用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)。
- 参数
text
,要被解析成 JavaScript 值的字符串。 - 参数
reviver
(可选)转换器,如果传入该参数(函数),可以用来修改解析生成的原始值,调用时机在 parse 函数返回之前。
五、其他
针对 Line separator 和 Paragraph separator 的处理,可以看这里:JSON.stringify用作 JavaScript
六、拓展
根据 ECMA-262 标准定义,一个字符串可以包含任何东西,只要它不是一个引号,一个反斜线或者一个行终止符。
以下被认为是行终止符:
\u000A
- Line Feed\u000D
- Carriage Return\u2028
- Line separator\u2029
- Paragraph separator
七、参考
- JSON MDN
- Introducing JSON
- ECMA-404 The JSON Data Interchange Standard
以上是 【JS】详谈JSON与JavaScript 的全部内容, 来源链接: utcz.com/a/102336.html