第 22 章 JSON
目錄
購買書籍
(廣告,請勿封鎖。)

第 22 章 JSON

JSON(JavaScript 物件表示法)是一種用於資料儲存的純文字格式。它已成為 Web 服務、組態檔等資料交換格式中相當受歡迎的選擇。ECMAScript 5 有一個 API,可將 JSON 格式的字串轉換為 JavaScript 值(剖析),反之亦然(字串化)。

背景

本節說明 JSON 是什麼以及它是如何建立的。

資料格式

JSON 將資料儲存為純文字。其語法是 JavaScript 表達式的語法子集。例如:

{
    "first": "Jane",
    "last": "Porter",
    "married": true,
    "born": 1890,
    "friends": [ "Tarzan", "Cheeta" ]
}

JSON 使用 JavaScript 表達式中的下列建構:

複合
JSON 資料物件和 JSON 資料陣列
原子
字串、數字、布林值和 null

它遵守下列規則

歷史

Douglas Crockford 在 2001 年發現了 JSON。他為其命名,並在 http://json.org 上建立了規格

我發現了 JSON。我並未宣稱自己發明了 JSON,因為它本來就存在於自然界中。我所做的是找到它、為它命名,並說明它的用途。我並未宣稱自己是第一個發現它的人;我知道在我在發現它的一年前,就已經有其他人發現了它。我發現的最早的例子是,早在 1996 年,就有 Netscape 的某個人使用 JavaScript 陣列文字來進行資料通訊,這至少在我想到這個點子之前五年。

最初,Crockford 希望將 JSON 命名為JavaScript 標記語言,但縮寫 JSML 已被JSpeech 標記語言使用。

JSON 規格已被翻譯成許多人類語言,現在也有許多程式語言的函式庫支援剖析和產生 JSON。

語法

Douglas Crockford 建立了一張 JSON 名片,正面印有標誌(請參閱 圖 22-1),背面則印有完整的語法(請參閱 圖 22-2)。這讓 JSON 有多麼簡單一目了然。

語法可以轉錄如下

物件

{ }

{ 成員 }

成員

配對

配對 , 成員

配對
字串 :
陣列

[ ]

[ 元素 ]

元素

, 元素

字串

數字

物件

陣列

true

false

null

字串

""

" 字元 "

字元

字元

字元 字元

字元

任何 Unicode 字元(不含 "、\ 或控制字元)

\" \\ \/ \b \f \n \r \t

\u 四個十六進位數字

數字

整數

整數 小數

整數 指數

整數 小數 指數

整數

數字

數字 1-9 數字

- 數字

- 數字 1-9 數字

小數
. 數字
指數
e 數字
數字

數字

數字 數字

e

e e+ e-

E E+ E-

全域變數 JSON 用作產生和剖析含有 JSON 資料的字串的函式名稱空間。

JSON.stringify(value, replacer?, space?)

JSON.stringify(value, replacer?, space?) 會將 JavaScript 值 value 轉換成 JSON 格式的字串。它有兩個選用引數。

選用參數 replacer 用於在字串化之前變更 value。它可以是

  • 一個 節點訪客(請參閱 透過節點訪客轉換資料),在字串化之前轉換值樹狀結構。例如

    function replacer(key, value) {
        if (typeof value === 'number') {
            value = 2 * value;
        }
        return value;
    }

    使用 replacer

    > JSON.stringify({ a: 5, b: [ 2, 8 ] }, replacer)
    '{"a":10,"b":[4,16]}'
  • 一個屬性金鑰白名單,用於隱藏所有金鑰不在清單中的屬性(非陣列物件)。例如

    > JSON.stringify({foo: 1, bar: {foo: 1, bar: 1}}, ['bar'])
    '{"bar":{"bar":1}}'

    白名單對陣列沒有影響

    > JSON.stringify(['a', 'b'], ['0'])
    '["a","b"]'

選用參數 space 會影響輸出的格式。沒有這個參數,stringify 的結果會是一行文字

> console.log(JSON.stringify({a: 0, b: ['\n']}))
{"a":0,"b":["\n"]}

有了它,就會插入換行符,而且透過陣列和物件進行的每一層巢狀結構都會增加縮排。有兩種方式可以指定縮排方式

數字

將數字乘以縮排層級,並使用相同數量的空格縮排該行。小於 0 的數字會解釋為 0;大於 10 的數字會解釋為 10

> console.log(JSON.stringify({a: 0, b: ['\n']}, null, 2))
{
  "a": 0,
  "b": [
    "\n"
  ]
}
字串

要縮排,請針對每一層縮排重複給定的字串一次。只會使用字串的前 10 個字元

> console.log(JSON.stringify({a: 0, b: ['\n']}, null, '|--'))
{
|--"a": 0,
|--"b": [
|--|--"\n"
|--]
}

因此,以下 JSON.stringify() 呼叫會將物件印成格式良好的樹狀結構

JSON.stringify(data, null, 4)

JSON.stringify() 忽略的資料

在物件中,JSON.stringify() 只會考慮可列舉的自身屬性(請參閱 屬性屬性和屬性描述詞)。以下範例示範了不可列舉的自身屬性 obj.foo 被忽略:

> var obj = Object.defineProperty({}, 'foo', { enumerable: false, value: 7 });
> Object.getOwnPropertyNames(obj)
[ 'foo' ]
> obj.foo
7
> JSON.stringify(obj)
'{}'

如何 JSON.stringify() 處理 JSON 不支援的值(例如函式和 undefined)取決於它在哪裡遇到這些值。一個不受支援的值本身會導致 stringify() 回傳 undefined,而不是字串

> JSON.stringify(function () {})
undefined

值不受支援的屬性會直接被忽略

> JSON.stringify({ foo: function () {} })
'{}'

陣列中不受支援的值會被字串化為 null

> JSON.stringify([ function () {} ])
'[null]'

toJSON() 方法

如果 JSON.stringify() 遇到一個 具有 toJSON 方法的物件,它會使用該方法取得要字串化的值。例如:

> JSON.stringify({ toJSON: function () { return 'Cool' } })
'"Cool"'

日期已經有一個 toJSON 方法 會產生 ISO 8601 日期字串:

> JSON.stringify(new Date('2011-07-29'))
'"2011-07-28T22:00:00.000Z"'

toJSON 方法的完整簽章如下

function (key)

key 參數允許您根據內容不同進行字串化。它永遠都是字串,並指出您的物件在父物件中的位置

根位置
空字串
屬性值
屬性鍵
陣列元素
元素的索引作為字串

我將透過下列物件示範 toJSON()

var obj = {
    toJSON: function (key) {
        // Use JSON.stringify for nicer-looking output
        console.log(JSON.stringify(key));
        return 0;
    }
};

如果你使用 JSON.stringify(),每個 obj 的出現都會被 0 取代。 toJSON() 方法會被通知在屬性鍵 'foo' 和陣列索引 0 中遇到 obj

> JSON.stringify({ foo: obj, bar: [ obj ]})
"foo"
"0"
'{"foo":0,"bar":[0]}'

內建的 toJSON() 方法如下所示:

  • Boolean.prototype.toJSON()
  • Number.prototype.toJSON()
  • String.prototype.toJSON()
  • Date.prototype.toJSON()

JSON.parse(text, reviver?)

JSON.parse(text, reviver?) 會分析 text 中的 JSON 資料,並傳回 JavaScript 值。以下是幾個範例:

> JSON.parse("'String'") // illegal quotes
SyntaxError: Unexpected token ILLEGAL
> JSON.parse('"String"')
'String'
> JSON.parse('123')
123
> JSON.parse('[1, 2, 3]')
[ 1, 2, 3 ]
> JSON.parse('{ "hello": 123, "world": 456 }')
{ hello: 123, world: 456 }

選用參數 reviver節點訪客(請參閱透過節點訪客轉換資料),可用於轉換已分析的資料。在此範例中,我們將日期字串轉換為日期物件:

function dateReviver(key, value) {
    if (typeof value === 'string') {
        var x = Date.parse(value);
        if (!isNaN(x)) { // valid date string?
            return new Date(x);
        }
    }
    return value;
}

以下是互動

> var str = '{ "name": "John", "birth": "2011-07-28T22:00:00.000Z" }';
> JSON.parse(str, dateReviver)
{ name: 'John', birth: Thu, 28 Jul 2011 22:00:00 GMT }

透過節點訪客轉換資料

JSON.stringify()JSON.parse()讓你透過傳入函式來轉換 JavaScript 資料:

  • JSON.stringify() 讓你可以在將 JavaScript 資料轉換為 JSON 之前變更它。
  • JSON.parse() 會分析 JSON,然後讓你後續處理結果的 JavaScript 資料。

JavaScript 資料是一個樹狀結構,其複合節點為陣列和物件,而其葉節點為基本值(布林值、數字、字串、null)。讓我們使用名稱 節點訪客 來表示您傳入的轉換函式。這些方法會反覆運算樹狀結構,並對每個節點呼叫訪客。然後,它有選項可以取代或刪除節點。節點訪客的簽章為

function nodeVisitor(key, value)

參數為

this
目前節點的父節點。
key
目前節點在其父節點內的鍵。 key 永遠是字串。
目前節點。

根節點 root 沒有父節點。當 root 被拜訪時,會為它建立一個偽父節點,而參數具有下列值

  • this{ '': root }
  • key''
  • valueroot

節點訪客有三個選項可以傳回值

  • 傳回 value,維持原狀。然後不會執行任何變更。
  • 傳回不同的值。然後會用它取代目前的節點。
  • 傳回 undefined。然後會移除節點。

以下是一個節點訪客範例。它會記錄傳遞給它的值。

function nodeVisitor(key, value) {
    console.log([
        // Use JSON.stringify for nicer-looking output
        JSON.stringify(this), // parent
        JSON.stringify(key),
        JSON.stringify(value)
    ].join(' # '));
    return value; // don't change node
}

讓我們使用這個函式來檢視 JSON 方法如何反覆運算 JavaScript 資料。

JSON.stringify()

首先是 特殊根節點,以字首反覆運算(父節點在子節點之前)。首先拜訪的節點永遠是偽根節點。每次呼叫後顯示的最後一行是 stringify() 傳回的字串:

> JSON.stringify(['a','b'], nodeVisitor)
{"":["a","b"]} # "" # ["a","b"]
["a","b"] # "0" # "a"
["a","b"] # "1" # "b"
'["a","b"]'

> JSON.stringify({a:1, b:2}, nodeVisitor)
{"":{"a":1,"b":2}} # "" # {"a":1,"b":2}
{"a":1,"b":2} # "a" # 1
{"a":1,"b":2} # "b" # 2
'{"a":1,"b":2}'

> JSON.stringify('abc', nodeVisitor)
{"":"abc"} # "" # "abc"
'"abc"'

JSON.parse()

首先是 葉節點,以字尾反覆運算(子節點在父節點之前)。最後拜訪的節點永遠是偽根節點。每次呼叫後顯示的最後一行是 parse() 傳回的 JavaScript 值:

> JSON.parse('["a","b"]', nodeVisitor)
["a","b"] # "0" # "a"
["a","b"] # "1" # "b"
{"":["a","b"]} # "" # ["a","b"]
[ 'a', 'b' ]

> JSON.parse('{"a":1, "b":2}', nodeVisitor)
{"a":1,"b":2} # "a" # 1
{"a":1,"b":2} # "b" # 2
{"":{"a":1,"b":2}} # "" # {"a":1,"b":2}
{ a: 1, b: 2 }

> JSON.parse('"hello"', nodeVisitor)
{"":"hello"} # "" # "hello"
'hello'

下一頁:23. 標準全域變數