我們先來看一個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物件的區別;
如果沒有去過JSON的官方介紹可以去一下這裡,官方介紹第一、二段已經很清楚地表述了JSON是什麼,我將JSON是什麼提煉成以下幾個面向:
什麼是格式?就是規範你的數據要怎麼表示,舉個栗子,有個人叫“二百六”,身高“160cm”,體重“60kg”,現在你要將這個人的這些信息傳給別人或者別的什麼東西,你有很多種選擇:
## name="二百六"&height="160cm"&weight="60kg"
# {"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處理請求為例:############ 前端建構一個JS對象,用於包裝要傳遞的數據,然後將JS對象轉化為JSON字串,再發送請求到後端;################### ######## 後端PHP接收到這個JSON字串,將JSON字串轉換為PHP對象,然後處理請求。 #### 可以看到,相同的資料在這裡有3種不同的表現形式,分別是前端的JS對象、傳輸的JSON字串、後端的PHP對象,JS對象和PHP對像明顯不是一個東西,但是由於大家用的都是JSON來傳遞數據,大家都能理解這種數據格式,都能把JSON這種數據格式很容易地轉化為自己能理解的數據結構,這就方便啦,在其他各種語言環境中交換數據都是如此。
# 很多時候都聽到「JSON是JS的子集」這句話,而且這句話我曾經也一直這麼認為,每個符合JSON格式的字串你解析成js都是可以的,直到後來發現了一個奇怪怪的東西...
# JSON和JS物件本質上完全不是同一個東西,就像“斑馬線”和“斑馬”,“斑馬線”基於“斑馬”身上的條紋來呈現和命名,但是斑馬是活的,斑馬線是非生物。
同樣,"JSON"全名"JavaScript Object Notation",所以它的格式(語法)是基於JS的,但它就是一種格式,而JS物件是一個實例,是存在於記憶體的東西。
說句玩笑,如果JSON是基於PHP的,可能就叫PON了,形式可能就是這樣的了['propertyOne' => 'foo', 'propertyTwo' => 42,],如果這樣,那麼JSON可能現在是和PHP比較密切了。
此外,JSON是可以傳輸的,因為它是文字格式,但是JS物件是沒辦法傳輸的,在語法上,JSON也會更加嚴格,但是JS物件就很鬆了。
那麼兩個不同的東西為什麼那麼密切,因為JSON畢竟是從JS演變出來的,語法相近。
先就以「鍵值對為表現的對象」形式上,對比下兩者的不同,至於JSON還能以怎樣的形式表現,對比完後再羅列。
# 對比內容 | JSON | JS物件 |
---|---|---|
#
鍵名 |
必須是加雙引號 |
可允許不加單引號、加雙引號 |
#
屬性值 |
只能是數值(10進位)、字串(雙引號)、布林值和null, |
愛啥啥 |
#
逗號問題 |
最後一個屬性後面不能有逗號 |
可以 |
#
數值 |
前導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格式的。
这里有完整的JSON语法参考
首先看下面的代码,你可以copy到控制台执行下:
var code = '"\u2028\u2029"'; JSON.parse(code); // works fine eval(code); // fails
这两个字符\u2028和\u2029分别表示行分隔符和段落分隔符,JSON.parse可以正常解析,但是当做js解析时会报错。
在JS中我们主要会接触到两个和JSON相关的函数,分别用于JSON字符串和JS数据结构之间的转化,一个叫JSON.stringify,它很聪明,聪明到你写的不符合JSON格式的JS对象都能帮你处理成符合JSON格式的字符串,所以你得知道它到底干了什么,免得它只是自作聪明,然后让你Debug long time;另一个叫JSON.parse,用于转化json字符串到JS数据结构,它很严格,你的JSON字符串如果构造地不对,是没办法解析的。
而它们的参数不止一个,虽然我们经常用的时候只传入一个参数。
此外,还有一个toJSON函数,我们较少看到,但是它会影响JSON.stringify。
这个函数的函数签名是这样的:
JSON.stringify(value[, replacer [, space]])
下面将分别展开带1~3个参数的用法,最后是它在序列化时做的一些“聪明”的事,要特别注意。
这个大家都会使用,传入一个JSON格式的JS对象或者数组,JSON.stringify({"name":"Good Man","age":18})返回一个字符串"{"name":"Good Man","age":18}"。
可以看到本身我们传入的这个JS对象就是符合JSON格式的,用的双引号,也没有JSON不接受的属性值,那么如果像开头那个例子中的一样,how to play?不急,我们先举简单的例子来说明这个函数的几个参数的意义,再来说这个问题。
如果第二个参数是一个函数,那么序列化过程中的每个属性都会被这个函数转化和处理
如果第二个参数是一个数组,那么只有包含在这个数组中的属性才会被序列化到最终的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-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} } */
笑笑就好,别这样用,序列化是为了传输,传输就是能越小越好,加莫名其妙的缩进符,解析困难(如果是字符串的话),也弱化了轻量化这个特点。。
如果有其他不确定的情况,那么最好的办法就是"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.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是最后解析完成对象,因为到了最上层,已经没有真正的属性了。
如果你在一个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,可以解決。
如有紕漏,歡迎留言指出。
以上是深入理解 JSON的詳細內容。更多資訊請關注PHP中文網其他相關文章!