JSON的本質是一個字符串,它之所以受歡迎,是因爲可以把JSON字符串解析爲有用的JavaScript對象。
ECMAScript5中,對解析JSON的行爲進行了規範,並定義了一個全局對象JSON,該對象有兩個方法:JSON.parse() 和 JSON.stringify()。
JSON.parse()
JSON.parse() 方法,用於把JSON字符串轉換爲原生的 JavaScript 值或對象。如:
JSON.parse('1') // 1
JSON.parse('{}') // {}
JSON.parse('true') // true
JSON.parse('"foo"') // "foo"
JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
JSON.parse('null') // null
var o = JSON.parse('{"name": "張三"}');
o.name // 張三
如果傳遞給JSON.parse()方法的字符串不是有效的JSON格式,JSON.parse()方法將報錯。如:
JSON.parse("'String'")
//Uncaught SyntaxError: Unexpected token u in JSON at position 0(…)
JSON.parse("undefined")
//Uncaught SyntaxError: Unexpected token u in JSON at position 0(…)
除了直接進行字符串轉換之外,JSON.parse()方法也可以接收一個函數參數,該函數被稱爲還原函數。如果提供還原函數,則對象的每個成員都會調用這個函數。
還原函數接收兩個參數,一個鍵和一個值,並返回一個值。如果還原函數返回undefined,則表示要從結果中刪除相應的鍵;如果返回其他值,則將該值插入到結果中。如:
var o = JSON.parse('{"a":1,"b":2}', function(key, value) {
if (key === ''){
return value;
}
if (key === 'a') {
return value + 10;
}
});
o.a // 11
o.b // undefined
JSON 不能存儲 Date 對象。如果你需要存儲 Date 對象,需要將其轉換爲字符串,之後再將字符串轉換爲 Date 對象。在將日期字符串轉換爲Date對象時,經常要用到還原函數。如:
var book = {
"title": "javascript",
"date": new Date(2018,1,21)
}
var jsonStr = JSON.stringify(book);
console.log(jsonStr)
//'{"title":"javascript","date":"2018-02-20T16:00:00.000Z"}'
var bookCopy = JSON.parse(jsonStr,function(key,value){
if(key == 'date'){
return new Date(value);
}
return value;
})
console.log(bookCopy.date.getFullYear()); //2018
說明:JSON.parse() 和 eval()
實際上,eval() 函數的功能類似於 JSON.parse()方法,也可以將json字符串轉換爲json對象。如:
eval('(' + '{"a":1}'+')').a; //1
JSON.parse('{"a":1}').a; //1
但是,eval() 函數可以執行不符合 JSON 格式的代碼。如:
eval('(' + '{"a":alert(1)}'+')').a; //彈出1
JSON.parse('{"a":alert(1)}').a; //報錯
在瀏覽器中運行上述代碼,eval() 函數就會彈出 alert 框,而 JSON.parse()方法將會報錯。由於 eval() 函數會爲惡意代碼提供可乘之機,因此應當儘量少使用 eval() 函數,而是使用更安全的 JSON.parse()方法。
JSON.stringify()
JSON.stringify()方法用於把JavaScript值(對象或者數組)序列化爲JSON字符串,並返回序列化後的JSON字符串。調用格式爲:
JSON.stringify(value[, replacer[, space]])
參數:
value: 必需。將要序列化爲JSON 字符串的值。
replacer: 可選。用於替換結果的函數、或僅包括指定的屬性數組。
space: 可選。用於文本添加縮進、空格和換行符。
序列化規則
序列化的本質,就是按照 JSON 的語法規則,把 JavaScript 值(對象或者數組),轉換爲 JSON 字符串,以方便存儲和數據交換。
1)數值、布爾值、字符串的包裝對象,會轉換成對應的原始值。如:
JSON.stringify(1) // "1"
JSON.stringify(false) // "false"
JSON.stringify('abc') // ""abc""
JSON.stringify([1, "false", false]) // '[1,"false",false]'
2)鍵和值都被序列化,如果鍵名沒有引號,會自動爲它添加引號。如:
JSON.stringify({x: 5, y: 6}); // "{"x":5,"y":6}"
JSON.stringify({name: "張三"}) // '{"name":"張三"}'
3)數組,會轉換成數組格式。如:
JSON.stringify([]) // "[]"
JSON.stringify([1, "false", false]) // '[1,"false",false]'
4)對象,會轉換成對象格式。如:
JSON.stringify({}) // "{}"
JSON.stringify({x: 5, y: 6}); // "{"x":5,"y":6}"
5)正則表達式和數學對象,轉換成空對象的字符串形式。如:
JSON.stringify(/foo/) // "{}"
JSON.stringify(Math) // "{}"
6)日期對象和包裝對象,轉換成字符串。如:
JSON.stringify(new Boolean(true)) //"true"
JSON.stringify(new String('123')) //""123""
JSON.stringify(new Number(1)) //"1"
JSON.stringify(new Date()) //""2016-09-20T02:26:38.294Z""
7) 如果非數組對象的成員是undefined、或任意的函數、或 symbol 值,這個成員會被省略。如果數組對象的成員是undefined、或任意的函數、或 symbol 值,則這些值被轉成null。如:
JSON.stringify({
a: function(){},
b: undefined,
c: [ function(){}, undefined ]
});
// "{"c":[null,null]}"
8)不可枚舉的屬性,會被忽略。如:
var obj = {};
Object.defineProperties(obj, {
'foo': { value: 1, enumerable: true },
'bar': { alue: 2, enumerable: false }
});
JSON.stringify(obj); // "{"foo":1}"
9)所有以 symbol 爲鍵的屬性都會被完全忽略掉,即便 replacer 參數中強制指定包含了它們。如:
JSON.stringify(
{[Symbol.for("foo")]: "foo"},
function (k, v) {
if (typeof k === "symbol"){
return "a symbol";
}
}
);
// undefined
replacer參數
replacer參數可以是一個函數或者一個數組。
1)函數
如果replacer參數是一個函數,則對象的每個成員都會調用這個函數。該函數接收兩個參數,一個是鍵(key),一個是值(value),執行序列化時,鍵和值都會被序列化。如:
function dobule(key, value){
if (typeof value === "number") {
value = 2 * value;
}
return value;
}
console.log(JSON.stringify({a:1,b:2}, dobule())); // "{"a":2,"b":4}"
如果replacer函數返回的是一個對象,則該對象被遞歸地序列化成JSON字符串,並且允許鍵爲空。如:
var jsonObj = {a: {b: 1}};
JSON.stringify(jsonObj, function(key, value) {
console.log("["+ key +"]:" + value);
return value;
});
上述代碼中,對象 jsonObj 一共會被 function函數處理三次。第一次鍵名爲空,鍵值是整個對象 jsonObj;第二次鍵名爲 a,鍵值是 {b:1};第三次鍵名爲 b,鍵值爲 1。運行結果爲:
[]:[object Object]
[a]:[object Object]
[b]:1
如果replacer函數返回了undefined或沒有返回值,那麼相應的屬性會被忽略,這種情況將不會被序列化成JSON字符串。如:
JSON.stringify({ a: "abc", b: 123 }, function (key, value) {
if (typeof(value) === "string") {
return undefined;
}
return value;
})
上述代碼得到的結果爲:
'{"b": 123}'
2)數組
如果replacer是一個數組,數組的值代表將被序列化成JSON字符串的屬性名。執行序列化時,則只對數組中所列舉的鍵執行序列化操作,就相當於實現一個過濾器的功能。如:
var jsonObj = {
"title":"javascript",
"group":{
"a":1,
"b":2
}
};
console.log(JSON.stringify(jsonObj,["group","b"]))
上述代碼的運行結果爲:
{"group":{"b":2}}
需要特別說明的是,過濾器功能只適用於對象,對數組無效。如,
var jsonObj = [1,2];
console.log(JSON.stringify(jsonObj,["0"])); // "[1,2]"
space參數
默認情況下,JSON.stringify() 輸出的JSON字符串不包括任何空格字符或縮進。如:
var jsonObj = {
"title":"javascript",
"group":{
"name":"jia",
"tel":12345
}
};
JSON.stringify(jsonObj);
//{"title":"javascript","group":{"name":"jia","tel":12345}}
通過指定space參數,可以在JSON字符串中添加空格或字符串,用於增加返回的JSON字符串的可讀性。space參數的值可以是數值或字符串。
1)數值
如果space參數的值是數值,則序列化的字符串中,每一級別會比上一級別縮進指定數目的空格(不超過10個空格)。如:
console.log(JSON.stringify({ p1: 1, p2: 2 }, null, 2));
上述代碼得到的結果爲:
{
"p1": 1,
"p2": 2
}
2)字符串
如果space參數的值是字符串(字符串不超過10個字符),則序列化的字符串中,每一級別會比上一級別的縮進,使用該字符串填充。如:
console.log(JSON.stringify({ p1:1, p2:2 }, null, '|-'));
上述代碼得到的結果爲:
{
|-"p1": 1,
|-"p2": 2
}
注意:
在序列化一個對象時,如果該對象擁有 toJSON()方法,那麼該 toJSON() 方法就會覆蓋該對象默認的序列化行爲: 被序列化的不是那個原始對象,而是調用 toJSON() 方法後返回的那個對象。如:
var obj = {
foo: 'foo',
toJSON: function () {
return 'bar';
}
};
JSON.stringify(obj); // '"bar"'
JSON.stringify({x: obj}); // '{"x":"bar"}'
如果 toJSON()方法返回 undefined,此時如果包含它的對象嵌入在另一個對象中,會導致該對象的值變成 null。而如果包含它的對象是頂級對象,結果就是 undefined。
Date 對象部署了一個自己的 toJSON()方法,它會自動將 Date 對象轉換成日期字符串。如:
JSON.stringify(new Date("2017-12-29"))
// "2017-12-29T00:00:00.000Z"
關於作者
歪脖先生,十五年以上軟件開發經驗,酷愛Web開發,精通 HTML、CSS、JavaScript、jQuery、JSON、Python、Less、Bootstrap等,著有《HTML寶典》、《揭祕CSS》、《Less簡明教程》、《JSON教程》、《Bootstrap2用戶指南》、《Bootstrap3實用教程》,並全部在 GitHub 上開源。