第 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 的每次出现都将替换为 0toJSON() 方法会收到通知,指示在属性键 '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. 标准全局变量