5、总结
What?一来就总结?好吧,因为在JS中使用JSON太简单。我在总结里三两句话都能说完,如果你有兴趣深入了解,再看1-4节的内容。
-
JSON是一种广泛应用与信息交换的轻量、文本数据格式。Web开发中,经常需要把数据转化为JSON格式再发送出去,或者从服务器接收到JSON格式的数据,再解析本地JS对象进行操作。
-
JS中内置的
JSON对象用于进行JSON转化。使用JSON.stringify()用于把JS对象或数组转化成JSON字符串,JSON.parse()用于从JSON字符串中解析出JS对象。简单的使用案例:let boy = { name: "czpcalm", isAdult: true, friend: ["cgc", "cyj", "czr", "cct"], write: function () { //函数属性被跳过 return "I am writing blog"; } } let boyString = JSON.stringify(boy); let boyParsed = JSON.parse(boyString); console.log(boyString); //{"name":"czpcalm","isAdult":true,"friend":["cgc","cyj","czr","cct"]} console.log(boyParsed); /* { name: 'czpcalm', isAdult: true, friend: ['cgc', 'cyj', 'czr', 'cct'] } */ console.log(boyParsed.name); //czpcalm -
在使用
JSON.stringify(obj)进行序列化(JSON化)的时候,会先调用obj.toJSON(),如果对象实现个toJSON()函数,用返回值作为对象的序列化结果,否则用默认的序列化规则。根据这个,可以通过为对象实现toJSON()方法实现自定义序列化。
1、什么是JSON
JSON是什么?一句话概括,就是一种广泛应用与信息交换的文本格式。
JSON(JavaScript Object Notation,JavaScript对象表示法)。是一种由道格拉斯·克罗克福特构想和设计、轻量级的资料交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。JSON格式是1999年《JavaScript Programming Language, Standard ECMA-262 3rd Edition》的子集合,不过这并不代表JSON无法使用于其他语言,事实上几乎所有与网页开发相关的语言都有JSON函数库。(维基百科)
从它的名称来看,JSON最初的目的是用于将JS对象以字符串的形式传输,实现数据的交换。后来,因为这种基于文本的格式十分轻量,又通用(各种语言都有字符串类型),并且,对象与数组的表示法非常适用于信息表示和理解,逐渐被多种语言采用,最终,JSON发展成为沟通语言之间信息交换的中间语言,渐渐从JS中独立出去。
就好像远古时期,不同语种的人相遇,无法语言沟通,唯有肢体动作是人类通用的,双方通过肢体语法去表达意思。JSON就是独立于各种语言,被大家广泛理解的“肢体语言”。
2、JSON的数据结构与格式
尽管JSON可以被称为语言,但它其实是简单的文本格式,不过,它确实用了不同格式表示了多种类型。
一个简单的JSON文件大概这样:
{
"name": "BeJso",
"url": "http://www.bej.com",
"page": 88,
"isNonProfit": true,
"services": [
"Web",
"Game",
"Android App"
],
"address": {
"street": "科技园路.",
"city": "江苏苏州",
"country": "中国"
},
"subCompony": null
}
即使你第一次看见,你也能从单词的意思里揣测出文本大概记录的信息,就是这么容易懂。
为了更好的理解JSON,我们需要掌握它的数据类型,下面是JS中的全部数据类型,你可以从上面的例子中找到:
字符串。JSON中最广泛应用的类型,用""包含,不支持单引号。数字。类似JS,没有整数与浮点数之分,使用双精度浮点数表示。布尔值。true和false。null。object。这里的object是与JS对应的,其它语言也有称为字典、映射、散列表之类的说法。用{}表示,对象中包含多个属性,属性以键: 值的形式表示,属性之间用,分隔,其中,键(属性名称)只能是字符串且必须用双引号括住,值可以是任何的数据类型。数组。用[]包括。数组元素可以是任何的数据类型。
需要注意的是,对象的属性名称(键)只能是字符串类型,且必须被""包含,如对象{name: "Mike", 'sex': "male"}JSON表示为{"name":"Mike", "sex":"male"}。
一些JS中上面没有提到的类型,都会被忽略或者转为其他类型。这将在介绍JSON.stringify()时说明。
3、在JS中使用JSON
使用JSON,无非是JS与JSON的转化问题。得益于JSON的简单,而且JSON本来就是JS的子集,在JS里使用JSON就跟老爸使唤儿子一样简单,不过这是一个有些自我想法的儿子。
JS中内置了一个JSON对象,提供JSON相关的操作, 主要方法是JSON.stringify(JSobj)和JSON.parse(jsonString),JSON.stringify()把JS对象转化为JSON格式,JSON.parse()把JSON格式的字符串解析成JS对象。你可以在任何地方使用它们。
3.1、JSON.stringify()和Object.toJSON()
把JS对象转化为JSON字符,这个过程称为序列化(常用),或者字符串化,或者JSON化,或者通俗得讲编码,其实都一个意思,who cares。
JS只需要一个方法就可以实现对象的序列化过程。
let JSONString = JSON.stringify(JSObj);
从这个方法名看采取了字符串化的说法。至于序列化的细节过程,由方法内部去执行。从前面的数据类型了解到,JSON并不囊括所有的数据类型,所以不被支持的数据类型会被自动转化或忽略。
-
String,Number,Boolean,null基本类型及其包装类型(如果存在)被支持,它们转化为对应的类型。特殊数值Infinity与NaN都会被转为null。 -
undefined,函数,Symbol类型都不被JSON支持,所以在序列化过程中,如果作为对象属性出现,该属性被跳过,如果作为数组成员出现,它们被转化为null。单独对这三个类型的对象进行转化会得到空字符串(被忽略)。 -
Date对象能被较好的转化为表示时间的字符串,而不是以{}的形式。 -
其它对象都只会序列化
自身的可枚举属性。,因为RegExp,Map,Set,WeakMap,WeakSet这些内置对象的属性都是不可枚举的,所以它们在一般情况下都丢失了信息,变成{}。
/*序列化对象*/
let obj = {
a: undefined,
[Symbol("b")]: "b", //键为Symbol
c: () => 2,
d: "d",
e: Symbol("e"), //值为Symbol
reg: /^\djs/, //正则
date: new Date() //日期
}
let jsonObj = JSON.stringify(obj);
console.log(jsonObj); //{"d":"d","reg":{},"date":"2020-09-18T07:03:36.263Z"}
/*序列化数组*/
let arr = [null, undefined, function () {
return 0;
}, Symbol(), new RegExp("a RegExp", "gi"), new Set([2, 3])];
let jsonArr = JSON.stringify(arr);
console.log(jsonArr); //[null,null,null,null,{},{}]
/*原型上的属性不会被序列化*/
let a = {
a: "a"
};
a.__proto__.b = "b";
console.log(JSON.stringify(a)); //{"a":"a"}
另外,你更需要格外注意两种引发错误的时候。
- 存在引用循环。抛出一个
Converting circular structure to JSON的TypeError尤其当对象直接的关系复杂的时候,循环引用的情况并不少见。 - 存在
BigInt类型,抛出一个Do not know how to serialize a BigInt的TypeError。
let objA = {},
objB = {};
objA.B = objB;
objB.A = objA;
JSON.stringify(objA); //TypeError: Converting circular structure to JSON
let objC = {
c: BigInt("222222222")
};
JSON.stringify(objC); //TypeError: Do not know how to serialize a BigInt
尽管上面看起来让我们感觉序列化的结果很难让我们满意,但你要知道,这是我故意为之,大部分情况下,序列化的目标都不会存在引发上面问题的因素,JSON.stringify()也可以达到令人满意的结果。
当然,有时候我们希望对象能以我们想要的格式进行系列化,类似我们可以通过修改toString()自定义对象的字符串转化,完全可以,你需要实现toJSON()方法。实现这个方法就跟实现toString()一样,返回的字符串将作为该对象序列化的结果。
let room = {
floor: "2"
number: "3",
capacity: "100"
};
let meeting = {
title: "Earn more",
time: "9:00 am - 2020/10/10",
room: room
}
console.log(JSON.stringify(meeting));
/*{"title":"Earn more","time":"9:00 am - 2020/10/10","room":{"floor":"2","number":"3","capacity":"100"}}*/
可以看到,引用值room也能被正确序列号。现在,假设我们并不关心room的具体信息,而是想要类似2 - 3的房间号,只需要实现room.toJSON()。
room.toJSON = function () {
return this.floor + " - " + this.number;
}
console.log(JSON.stringify(meeting));
//{"title":"Earn more","time":"9:00 am - 2020/10/10","room":"2 - 3"}
很好!顺便一提,Date对象就是实现了toJSON方法才能正确转化成字符串的。
可是,你写完之后,你同事突然发消息告诉你,他那边JSON.stringify(room)怎么是这奇怪的东西?他要room对象的详细信息。可是有了因为你定义了room.toJSON(),任何时候,序列化的的结果都是该函数的返回值。
这个时候,你也许需要认识JSON.stringify()的全貌了。
JSON.stringify(value[, replacer[, space]])
第一个参数很熟悉了,现在看第二个参数。它的作用是在本次序列化中替换某些键值对的系列化规则。我测试了一下,该参数只对对象有效,在数组序列化的时候被忽略。
当replacer是
- 一个字符串数组时,表示过滤掉键不在数组内的键值对,这种过滤效果是递归的,内层的键也会被过滤。
- 一个方法时,这个方法接收两个参数
(key, value),将每对键值对(不论value为什么类型,但key必须是字符串类型)传入replacer函数,函数的返回值是string,number,boolean或它们的包装对象或null时,作为该键的值undefined,函数,Symbol时,该键值对被忽略。- 其它对象,继续序列化,且递归地在键值对上调用
replacer
let object = {
a: 'a',
b: 'b',
c: {
d: "d",
e: 'e'
}
};
console.log(JSON.stringify(object, ["a", "c", "e"]));
//{"a":"a","c":{"e":"e"}} 注意d属性并没有被序列化
/*让undefined,Function,Symbol值的属性不被忽略*/
let obj = {
a: undefined,
[Symbol("b")]: "b", //键为Symbol
c: () => 2,
d: "d",
e: Symbol("e"), //值为Symbol
reg: /^\djs/, //正则
date: new Date() //日期
}
let jsonObj = JSON.stringify(obj, (key, value) => {
if (value === undefined) return null;
if (typeof value === "symbol") return "Symbol";
if (typeof value === "function") return "Function";
return value;
});
//{"a":null,"c":"Function","d":"d","e":"Symbol","reg":{},"date":"2020-09-18T08:50:37.815Z"}
掌握这点之后,我们就可以很简单地解决meeting序列化的问题了。实现toJSON()意味着改变每次序列化的结果,而传递replacer意味着只在本次序列化过程中替换。 显然, 上面的例子不应该实现toJSON()。而如果这样序列化,同事也不会找你麻烦了:
JSON.stringify(meeting,
(key, value) => (key === "room" ? value.floor + " - " + value.number : value)
);
另外,这种特性还可以避免序列化存在循环引用的对象时候产生错误,你只需要在循环引用的属性上返回undefined即可。
JSON.stringify()的最后一个参数place,决定了序列化时的缩进空格数量。果然程序员强迫症是全球统一的。
更多内容JSON.stringify。
3.2、JSON.parse()
反过来,从JSON格式到JS对象的过程,称为反序列化,也称为解析。解析的过程也只需要一个函数JSON.parse()。parse词义即是解析。
let JSObj = JSON.parse(JSONString);
更让人开心的是,任何合法的JSON数据类型都是合法的JS类型。所以,只要JSON字符串没有格式问题,解析的过程就没有问题。
不过,有时候你可能需要用到它的第二个参数reviver,它允许我们在对象返回前做一些改动。看下面的问题:
let diary = {
title: "A Happy Day",
date: new Date()
}
let diaryString = JSON.stringify(diary);
let refetchDiary = JSON.parse(diaryString);
console.log(refetchDiary.date.getDay());
//TypeError: refetchDiary.date.getDay is not a function
我把写好的日记保存成JSON,后来回顾的时候居然没办法确定是哪天写的了?这个问题,其实是Date类型的toJSON()把日期序列化为字符串(为了方便其他语言使用)。但是解析的时候,解析器根本无法确定哪个字符串是时间,所以还是保留字符串类型,说明toJSON是单向的。
为了解决这个问题,我们需要手动干预解析过程,这就是reviver参数的意义,它的原理与参数列表类似replacer。这里,我们需要对date属性做一个从字符串到Date对象的转化即可:
let refetchDiary = JSON.parse(diaryString,
(key, value) => (key === "date" ? new Date(value) : value)
);
console.log(refetchDiary.date.getDay()); //5
更多阅读JSON.parse()。
4、JSON的应用与JSON工具(拓展)
JSON数据在平时有着广泛的应用,也衍生出很多的JSON工具集,这些工具集大多以在线的形式呈现。比如BEJSON(随便google来的),有JSON检查器,查看器,编辑器,转化器等工具,如果你经常处理JSON格式,你一定不陌生。
JSON在Web信息交换方面的应用是任何一个接触Web开发的人都深有体会的,无论是后端服务器返回的数据,还是前端提交的数据,大部分都是JSON格式,这也是JSON最常见的应用场景。你可以点击这个Github trending的API。会看到一个Github服务器返回的JSON格式的数据,它可能是Unicode编码的,而且密密麻麻,看不懂,你可以使用上面提到的JSON查看器,就能看到其实里面记录了很多热门项目的信息。
除了用于信息交换,JSON这种易于读取与转化特点,也说明它很适合用于存储少量的记录信息。比如在一些非关系数据库中的数据存储、游戏中的信息保存、软件的配置信息(如VSCode settings)等。
总结请回头!完,谢谢阅读!