JSON.stringify和JSON.parse语法实例讲解

我们先来看一个JS中常见的JS对象序列化成JSON字符串的问题,请问,以下JS对象通过JSON.stringify后的字符串是怎样的?先不要急着复制粘贴到控制台,先自己打开一个代码编辑器或者纸,写写看,写完再去仔细对比你的控制台输出,如果有误记得看完全文并评论,哈哈。

var friend={  

   firstName: 'Good',

   'lastName': 'Man',

   'address': undefined,

   'phone': ["1234567",undefined],

   'fullName': function(){

       return this.firstName + ' ' + this.lastName;

   }

};

JSON.stringify(friend);//这一行返回什么呢?  

第二个问题,如果我想在最终JSON字符串将这个’friend’的姓名全部变成大写字母,也就是把”Good”变成”GOOD”,把”Man”变成”MAN”,那么可以怎么做?

基于以上两个问题,我们再追本溯源问一下,JSON究竟是什么东西?为什么JSON就是易于数据交换?JSON和JS对象的区别?JS中JSON.parse、JSON.stringify和不常见的toJSON,这几个函数的参数和处理细节到底是怎样的?

欢迎进入本次“深挖JSON之旅”,下文将从以下几个方面去理解JSON:

  • 首先是对“JSON是一种轻量的数据交换格式”的理解;

  • 然后来看经常被混为一谈的JSON和JS对象的区别;

  • 最后我们再来看JS中这几个JSON相关函数具体的执行细节。

希望全文能让如之前的我一样对JSON一知半解的亲能说清楚JSON是什么,也能熟练运用JSON,不看控制台就知道JS对象序列化成JSON字符串后输出是啥。

一、JSON是一种格式,基于文本,优于轻量,用于交换数据

如果没有去过JSON的官方介绍可以去一下这里,官方介绍第一、二段已经很清楚地表述了JSON是什么,我将JSON是什么提炼成以下几个方面:

1. 一种数据格式 
什么是格式?就是规范你的数据要怎么表示,举个栗子,有个人叫“二百六”,身高“160cm”,体重“60kg”,现在你要将这个人的这些信息传给别人或者别的什么东西,你有很多种选择:

  • 姓名“二百六”,身高“160cm”,体重“60kg”

  • name=”二百六”&height=”160cm”&weight=”60kg”

  • 二百六16060
  • {“name”:”二百六”,”height”:160,”weight”:60}

  • … …

以上所有选择,传递的数据是一样的,但是你可以看到形式是可以各式各样的,这就是各种不同格式化后的数据,JSON是其中一种表示方式。

2. 基于文本的数据格式 
JSON是基于文本的数据格式,相对于基于二进制的数据,所以JSON在传递的时候是传递符合JSON这种格式(至于JSON的格式是什么我们第二部分再说)的字符串,我们常会称为“JSON字符串”。

3. 轻量级的数据格式 
在JSON之前,有一个数据格式叫xml,现在还是广泛在用,但是JSON更加轻量,如xml需要用到很多标签,像上面的例子中,你可以明显看到xml格式的数据中标签本身占据了很多空间,而JSON比较轻量,即相同数据,以JSON的格式占据的带宽更小,这在有大量数据请求和传递的情况下是有明显优势的。

4. 被广泛地用于数据交换 
轻量已经是一个用于数据交换的优势了,但更重要的JSON是易于阅读、编写和机器解析的,即这个JSON对人和机器都是友好的,而且又轻,独立于语言(因为是基于文本的),所以JSON被广泛用于数据交换。

以前端JS进行ajax的POST请求为例,后端PHP处理请求为例: 
1.前端构造一个JS对象,用于包装要传递的数据,然后将JS对象转化为JSON字符串,再发送请求到后端; 
2.后端PHP接收到这个JSON字符串,将JSON字符串转化为PHP对象,然后处理请求。

可以看到,相同的数据在这里有3种不同的表现形式,分别是前端的JS对象、传输的JSON字符串、后端的PHP对象,JS对象和PHP对象明显不是一个东西,但是由于大家用的都是JSON来传递数据,大家都能理解这种数据格式,都能把JSON这种数据格式很容易地转化为自己能理解的数据结构,这就方便啦,在其他各种语言环境中交换数据都是如此。

二、JSON和JS对象之间的“八卦”

很多时候都听到“JSON是JS的一个子集”这句话,而且这句话我曾经也一直这么认为,每个符合JSON格式的字符串你解析成js都是可以的,直到后来发现了一个奇奇怪怪的东西…

1. 两个本质不同的东西为什么那么密切 
JSON和JS对象本质上完全不是同一个东西,就像“斑马线”和“斑马”,“斑马线”基于“斑马”身上的条纹来呈现和命名,但是斑马是活的,斑马线是非生物。

同样,”JSON”全名”JavaScript Object Notation”,所以它的格式(语法)是基于JS的,但它就是一种格式,而JS对象是一个实例,是存在于内存的一个东西。

说句玩笑话,如果JSON是基于PHP的,可能就叫PON了,形式可能就是这样的了[‘propertyOne’ => ‘foo’, ‘propertyTwo’ => 42,],如果这样,那么JSON可能现在是和PHP比较密切了。

此外,JSON是可以传输的,因为它是文本格式,但是JS对象是没办法传输的,在语法上,JSON也会更加严格,但是JS对象就很松了。

那么两个不同的东西为什么那么密切,因为JSON毕竟是从JS中演变出来的,语法相近。

2. JSON格式别JS对象语法表现上严格在哪 
先就以“键值对为表现的对象”形式上,对比下两者的不同,至于JSON还能以怎样的形式表现,对比完后再罗列。

对比内容 JSON JS对象
键名 必须是加双引号

可允许不加、加单引号、加双引号

属性值

只能是数值(10进制)、

字符串(双引号)、

布尔值和null

也可以是数组或者符合

JSON要求的对象,

不能是函数、NaN, Infinity,

 -Infinity和undefined

爱啥啥
逗号问题 最后一个属性后面不能有逗号 可以
数值 前导0不能用,小数点后必须有数字 没限制

可以看到,相对于JS对象,JSON的格式更严格,所以大部分写的JS对象是不符合JSON的格式的。

以下代码引用

var obj1 = {}; // 这只是 JS 对象

 

// 可把这个称做:JSON 格式的 JavaScript 对象

var obj2 = {"width":100,"height":200,"name":"rose"};

 

// 可把这个称做:JSON 格式的字符串

var str1 = '{"width":100,"height":200,"name":"rose"}';

 

// 这个可叫 JSON 格式的数组,是 JSON 的稍复杂一点的形式

var arr = [  

   {"width":100,"height":200,"name":"rose"},

   {"width":100,"height":200,"name":"rose"},

   {"width":100,"height":200,"name":"rose"},

];

 

// 这个可叫稍复杂一点的 JSON 格式的字符串    

var str2='['+  

   '{"width":100,"height":200,"name":"rose"},'+

   '{"width":100,"height":200,"name":"rose"},'+

   '{"width":100,"height":200,"name":"rose"},'+

']';  

另外,除了常见的“正常的”JSON格式,要么表现为一个对象形式{…},要么表现为一个数组形式[…],任何单独的一个10进制数值、双引号字符串、布尔值和null都是有效符合JSON格式的。

3. 一个有意思的地方,JSON不是JS的子集 
首先看下面的代码,你可以copy到控制台执行下:

var code = '"\u2028\u2029"';  

JSON.parse(code); // works fine  

eval(code); // fails  

这两个字符\u2028和\u2029分别表示行分隔符和段落分隔符,JSON.parse可以正常解析,但是当做js解析时会报错。

三、这几个JS中的JSON函数,弄啥嘞

在JS中我们主要会接触到两个和JSON相关的函数,分别用于JSON字符串和JS数据结构之间的转化,一个叫JSON.stringify,它很聪明,聪明到你写的不符合JSON格式的JS对象都能帮你处理成符合JSON格式的字符串,所以你得知道它到底干了什么,免得它只是自作聪明,然后让你Debug long time;另一个叫JSON.parse,用于转化json字符串到JS数据结构,它很严格,你的JSON字符串如果构造地不对,是没办法解析的。

而它们的参数不止一个,虽然我们经常用的时候只传入一个参数。

此外,还有一个toJSON函数,我们较少看到,但是它会影响JSON.stringify。

1. 将JS数据结构转化为JSON字符串——JSON.stringify 
这个函数的函数签名是这样的:

JSON.stringify(value[, replacer [, space]])  

下面将分别展开带1~3个参数的用法,最后是它在序列化时做的一些“聪明”的事,要特别注意。

1.1 基本使用——仅需一个参数 
这个大家都会使用,传入一个JSON格式的JS对象或者数组,JSON.stringify({“name”:”Good Man”,”age”:18})返回一个字符串”{“name”:”Good Man”,”age”:18}”。

可以看到本身我们传入的这个JS对象就是符合JSON格式的,用的双引号,也没有JSON不接受的属性值,那么如果像开头那个例子中的一样,how to play?不急,我们先举简单的例子来说明这个函数的几个参数的意义,再来说这个问题。

1.2 第二个参数可以是函数,也可以是一个数组

  • 如果第二个参数是一个函数,那么序列化过程中的每个属性都会被这个函数转化和处理

  • 如果第二个参数是一个数组,那么只有包含在这个数组中的属性才会被序列化到最终的JSON字符串中

  • 如果第二个参数是null,那作用上和空着没啥区别,但是不想设置第二个参数,只是想设置第三个参数的时候,就可以设置第二个参数为null

这第二个参数若是函数

var friend={  

   "firstName": "Good",

   "lastName": "Man",

   "phone":"1234567",

   "age":18

};

 

var friendAfter=JSON.stringify(friend,function(key,value){  

   if(key==="phone")

       return "(000)"+value;

   else if(typeof value === "number")

       return value + 10;

   else

       return value; //如果你把这个else分句删除,那么结果会是undefined

});

 

console.log(friendAfter);  

//输出:{"firstName":"Good","lastName":"Man","phone":"(000)1234567","age":28}

如果制定了第二个参数是函数,那么这个函数必须对每一项都有返回,这个函数接受两个参数,一个键名,一个是属性值,函数必须针对每一个原来的属性值都要有新属性值的返回。

那么问题来了,如果传入的不是键值对的对象形式,而是方括号的数组形式呢?,比如上面的friend变成这样:friend=[“Jack”,”Rose”],那么这个逐属性处理的函数接收到的key和value又是什么?如果是数组形式,那么key是索引,而value是这个数组项,你可以在控制台在这个函数内部打印出来这个key和value验证。

-这第二个参数若是数组-

var friend={  

   "firstName": "Good",

   "lastName": "Man",

   "phone":"1234567",

   "age":18

};

 

//注意下面的数组有一个值并不是上面对象的任何一个属性名

var friendAfter=JSON.stringify(friend,["firstName","address","phone"]);

 

console.log(friendAfter);  

//{"firstName":"Good","phone":"1234567"}

//指定的“address”由于没有在原来的对象中找到而被忽略

如果第二个参数是一个数组,那么只有在数组中出现的属性才会被序列化进结果字符串,只要在这个提供的数组中找不到的属性就不会被包含进去,而这个数组中存在但是源JS对象中不存在的属性会被忽略,不会报错。

1.3 第三个参数用于美化输出——不建议用 
指定缩进用的空白字符,可以取以下几个值:

  • 是1-10的某个数字,代表用几个空白字符

  • 是字符串的话,就用该字符串代替空格,最多取这个字符串的前10个字符

  • 没有提供该参数 等于 设置成null 等于 设置一个小于1的数

var friend={  

   "firstName": "Good",

   "lastName": "Man",

   "phone":{"home":"1234567","work":"7654321"}

};

 

//直接转化是这样的:

//{"firstName":"Good","lastName":"Man","phone":{"home":"1234567","work":"7654321"}}

 

var friendAfter=JSON.stringify(friend,null,4);  

console.log(friendAfter);  

/*

{

   "firstName": "Good",

   "lastName": "Man",

   "phone": {

       "home": "1234567",

       "work": "7654321"

   }

}

*/

 

var friendAfter=JSON.stringify(friend,null,"HAHAHAHA");  

console.log(friendAfter);  

/*

{

HAHAHAHA"firstName": "Good",  

HAHAHAHA"lastName": "Man",  

HAHAHAHA"phone": {  

HAHAHAHAHAHAHAHA"home": "1234567",  

HAHAHAHAHAHAHAHA"work": "7654321"  

HAHAHAHA}  

}

*/

 

var friendAfter=JSON.stringify(friend,null,"WhatAreYouDoingNow");  

console.log(friendAfter);  

/* 最多只取10个字符

{

WhatAreYou"firstName": "Good",  

WhatAreYou"lastName": "Man",  

WhatAreYou"phone": {  

WhatAreYouWhatAreYou"home": "1234567",  

WhatAreYouWhatAreYou"work": "7654321"  

WhatAreYou}  

}

*/

笑笑就好,别这样用,序列化是为了传输,传输就是能越小越好,加莫名其妙的缩进符,解析困难(如果是字符串的话),也弱化了轻量化这个特点。。

1.4 注意这个函数的“小聪明”(重要) 
如果有其他不确定的情况,那么最好的办法就是”Have a try”,控制台做下实验就明了。

  • 键名不是双引号的(包括没有引号或者是单引号),会自动变成双引号;字符串是单引号的,会自动变成双引号

  • 最后一个属性后面有逗号的,会被自动去掉

  • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中 
    这个好理解,也就是对非数组对象在最终字符串中不保证属性顺序和原来一致

  • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值 
    也就是你的什么new String(“bala”)会变成”bala”,new Number(2017)会变成2017

  • undefined、任意的函数(其实有个函数会发生神奇的事,后面会说)以及 symbol 值(symbol详见ES6对symbol的介绍)

    • 出现在非数组对象的属性值中:在序列化过程中会被忽略

    • 出现在数组中时:被转换成 null

JSON.stringify({x: undefined, y: function(){return 1;}, z: Symbol("")});  

//出现在非数组对象的属性值中被忽略:"{}"

JSON.stringify([undefined, Object, Symbol("")]);  

//出现在数组对象的属性值中,变成null:"[null,null,null]"

  • NaN、Infinity和-Infinity,不论在数组还是非数组的对象中,都被转化为null

  • 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们

  • 不可枚举的属性会被忽略

补充:

语法: 
  JSON.stringify(value [, replacer] [, space]) 

  • value:是必选字段。就是你输入的对象,比如数组,类等。 
  • replacer:这个是可选的。它又分为2种方式,一种是数组,第二种是方法。 

  情况一:replacer为数组时,通过后面的实验可以知道,它是和第一个参数value有关系的。一般来说,系列化后的结果是通过键值对来进行表示的。 所以,如果此时第二个参数的值在第一个存在,那么就以第二个参数的值做key,第一个参数的值为value进行表示,如果不存在,就忽略。

  情况二:replacer为方法时,那很简单,就是说把系列化后的每一个对象(记住是每一个)传进方法里面进行处理。 

  • space:就是用什么来做分隔符的。 

  1)如果省略的话,那么显示出来的值就没有分隔符,直接输出来 。
  2)如果是一个数字的话,那么它就定义缩进几个字符,当然如果大于10 ,则默认为10,因为最大值为10。
  3)如果是一些转义字符,比如“\t”,表示回车,那么它每行一个回车。 
  4)如果仅仅是字符串,就在每行输出值的时候把这些字符串附加上去。当然,最大长度也是10个字符。 
下面用实例说明;

  1)只有第一个参数的情况下

    //方式一:一个参数
    var student = new Object();
    student.name = 'JSON';
    student.age = 20;
    student.location = 'China';
    var json = JSON.stringify(student);
    alert("json:" + json);


有些人可能会怀疑JSON.stringify的作用。那假如,我们不要这个函数,而直接alert(student),结果如下:

直接只是说是object对象类型,具体的属性没有显示:


 这次意识到JSON.stringify的作用了吧。

  2)第二个参数存在,并且第二个参数是function的时候 


序列化数组:

    var arr = ['a','b','c'];
    var json3 = JSON.stringify(arr,switchUpper2);
    function switchUpper2(key, value) {
        console.log("key:" + key + "--value:" + value+"--:"+JSON.stringify(value));
        //key:--value:a,b,c--:["a","b","c"]
        return value.toString().toLocaleUpperCase();
    }
    alert("json3:" + json3);

 
 
序列化对象:
    var student2 = new Object();
    student2.name = 'JSON';
    student2.age = 20;
    student2.location = 'China';
    var json2 = JSON.stringify(student2, switchUpper);
    function switchUpper(key, value) {
        console.log("key:" + key + "--value:" + value+"--:"+JSON.stringify(value));
        //key:--value:[object Object]--:{"name":"JSON","age":20,"location":"China"}
        return value.toString().toLocaleUpperCase();
    }
    alert("json2:" + json2);

第二个参数中的value包含了整个对象引用,所以alert出来还是整个对象:


综上:key没有被赋予到值,只有value(第二个形参才有),方法中第三个形参之后值为:undefined


 3)第二个参数存在,并且第二个参数不是function,而是数组的时候

    var arr2 = ['abc', 'def', 'ghi'];
    var arr3 = ['1','2'];
    var json4 = JSON.stringify(arr2,arr3);
    alert(json4);
运行结果(和第二个参数为空时结果一样):
结论:第二个参数被忽略了,只是第一个参数被系列化了。

4)如果第一个参数是对象,第二个参数是数组的情况
 var arr2 = ['abc', 'def', 'ghi'];
    var arr3 = ['1','2'];
    var json4 = JSON.stringify(arr2,arr3);
    alert(json4);

    //方式四:第一个参数为对象
    var oo = {name : "Google", type : "ItNetwork",age : 20};

    var arr4 = ["age","pig","name"];
//    var json5 = JSON.stringify(oo,arr4);
    var json5 = JSON.stringify(oo,arr4,1000);
    //var json5 = JSON.stringify(oo,arr4,'\t');
    //var json5 = JSON.stringify(oo,arr4,'OK ');


序列化顺序按照数组中匹配的key进行。

2. 将JSON字符串解析为JS数据结构——JSON.parse 
这个函数的函数签名是这样的:

JSON.parse(text[, reviver])  

如果第一个参数,即JSON字符串不是合法的字符串的话,那么这个函数会抛出错误,所以如果你在写一个后端返回JSON字符串的脚本,最好调用语言本身的JSON字符串相关序列化函数,而如果是自己去拼接实现的序列化字符串,那么就尤其要注意序列化后的字符串是否是合法的,合法指这个JSON字符串完全符合JSON要求的严格格式。

值得注意的是这里有一个可选的第二个参数,这个参数必须是一个函数,这个函数作用在属性已经被解析但是还没返回前,将属性处理后再返回。

var friend={  

   "firstName": "Good",

   "lastName": "Man",

   "phone":{"home":"1234567","work":["7654321","999000"]}

};

 

//我们先将其序列化

var friendAfter=JSON.stringify(friend);  

//'{"firstName":"Good","lastName":"Man","phone":{"home":"1234567","work":["7654321","999000"]}}'

 

//再将其解析出来,在第二个参数的函数中打印出key和value

JSON.parse(friendAfter,function(k,v){  

   console.log(k);

   console.log(v);

   console.log("----");

});

/*

firstName  

Good  

----

lastName  

Man  

----

home  

1234567  

----

0  

7654321  

----

1  

999000  

----

work  

[]

----

phone  

Object  

----

 

Object  

----

*/

仔细看一下这些输出,可以发现这个遍历是由内而外的,可能由内而外这个词大家会误解,最里层是内部数组里的两个值啊,但是输出是从第一个属性开始的,怎么就是由内而外的呢?

这个由内而外指的是对于复合属性来说的,通俗地讲,遍历的时候,从头到尾进行遍历,如果是简单属性值(数值、字符串、布尔值和null),那么直接遍历完成,如果是遇到属性值是对象或者数组形式的,那么暂停,先遍历这个子JSON,而遍历的原则也是一样的,等这个复合属性遍历完成,那么再完成对这个属性的遍历返回。

本质上,这就是一个深度优先的遍历。

有两点需要注意:

  • 如果 reviver 返回 undefined,则当前属性会从所属对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值。

  • 你可以注意到上面例子最后一组输出看上去没有key,其实这个key是一个空字符串,而最后的object是最后解析完成对象,因为到了最上层,已经没有真正的属性了。


补充:

JSON.parse(text, reviver)

This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.


The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
参数
text
必需。 一个有效的 JSON 字符串。

reviver
可选。 一个转换结果的函数。 将为对象的每个成员调用此函数。 如果成员包含嵌套对象,则先于父对象转换嵌套对象。 对于每个成员,会发生以下情况:
如果 reviver 返回一个有效值,则成员值将替换为转换后的值。
如果 reviver 返回它接收的相同值,则不修改成员值。
如果 reviver 返回 null 或 undefined,则删除成员。

返回值
一个对象或数组。

可能抛出异常
语法错误:解析的字符串格式不正确等

1、将 JSON 字符串转换为对象
var jsontext = '{"firstname":"Jesper","surname":"Aaberg","phone":["555-0100","555-0120"]}';
var contact = JSON.parse(jsontext);
alert(contact.firstname + "--" + contact.phone);

如果字符串格式不对:
Uncaught SyntaxError: Unexpected number in JSON at position 61

2、反序列化 ISO 格式的日期字符串, 将返回Date格式对象
var jsontext2 = '{ "hiredate": "2008-01-01T12:00:00Z", "birthdate": "2008-12-25T12:00:00Z" }';
    var dates = JSON.parse(jsontext2, dateReviver);
    function dateReviver(key, value) {
        var a;
        if (typeof value === 'string') {
            a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
            if (a) {
                return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                        +a[5], +a[6]));
            }
        }
        return value;
    }
    ;
    alert("dates.birthdate.toUTCString():" + dates.hiredate.toUTCString());



3. 影响 JSON.stringify 的神奇函数——object.toJSON 
如果你在一个JS对象上实现了toJSON方法,那么调用JSON.stringify去序列化这个JS对象时,JSON.stringify会把这个对象的toJSON方法返回的值作为参数去进行序列化。

var info={  

   "msg":"I Love You",

   "toJSON":function(){

       var replaceMsg=new Object();

       replaceMsg["msg"]="Go Die";

       return replaceMsg;

   }

};

 

JSON.stringify(info);  

//出si了,返回的是:'"{"msg":"Go Die"}"',说好的忽略函数呢

这个函数就是这样子的。

其实Date类型可以直接传给JSON.stringify做参数,其中的道理就是,Date类型内置了toJSON方法。

四、小结以及关于兼容性的问题

到这里终于把,JSON和JS中的JSON,梳理了一遍,也对里面的细节和注意点进行了一次遍历,知道JSON是一种语法上衍生于JS语言的一种轻量级的数据交换格式,也明白了JSON相对于一般的JS数据结构(尤其是对象)的差别,更进一步,仔细地讨论了JS中关于JSON处理的3个函数和细节。

不过遗憾的是,以上所用的3个函数,不兼容IE7以及IE7之前的浏览器。有关兼容性的讨论,留待之后吧。如果想直接在应用上解决兼容性,那么可以套用JSON官方的js,可以解决。

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值