JSON(JavaScript Object Notation)简介

JSON是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

JSON 数据格式与语言无关,脱胎于 JavaScript,但目前很多编程语言都支持 JSON 格式数据的生成和解析。JSON 的官方 MIME 类型是 application/json,文件扩展名是 .json。

JSON出现之前,XML一直是互联网传输结构化数据的标准。然而业界不少人认为XML过于烦琐,冗长。为了解决些问题JSON应机而生。

JSON与XML最大的不同在于XML是一个完整的标记语言,而JSON不是。这使得XML在程序判读上需要比较多的功夫。主要的原因在于XML的设计理念与JSON不同。XML利用标记语言的特性提供了绝佳的延展性(如XPath),在数据存储,扩展及高级检索方面具备对JSON的优势,而JSON则由于比XML更加小巧,以及浏览器的内建快速解析支持,使得其更适用于网络数据传输领域。

关于JSON,最重要的是理解它是一种数据格式,不是一种编程语言。虽然JSON与Javascript有几乎完全相似的语法形式,但JSON并不从属于Javascript。并非只有Javascript才使用JSON,很多语言都有针对JSON的解析器和序列化器。

它基于 JavaScript 语法,但与之不同:一些JavaScript不是JSON,一些JSON不是JavaScript。参考 JSON:不是JavaScript 的子集

语法

JSON语法可以表示以下三种类型的值:

  • 简单值:使用与Javascript相同的语法,可以在JSON中表示字符串,数值,布尔值和null。注意JSON不支持Javascript中的特殊对象undefined.
  • 对象:一个对象包含一系列无序的名称/值对(pair),一个对象以{开始,并以}结束。每个名称/值对之间使用,分区。
  • 一个数组是一个值(value)的集合,一个数组以[,并已]结束。值之间使用,分区。

简单值

1
2
3
4
5
//数据值
666

//字符串
“testing”

JSON字符串与Javascript字符串最大的区别在于,JSON字符串必须使用双引号(单引号会导致语法错误)。

布尔值与null也是JSON的有效形式。实际应该用它们只是复杂的数据结构当中的一部分。

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Javascript
var person = {
name: 'ryan',
age: 6
};

var person = {
"name": "ryan",
"age": 6
};

//JSON
{
"name": "ryan",
"age": 6
}
  • JSON中的对象要求给属性加双引号(不加或单引号都会导致语法错误)。
  • JSON当中没有变量的概念。
  • JSON没有末尾的分号。
  • JSON对象最后一个属性不可以有逗号。(对象中嵌套的对象也一样)

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// javascript
var array = [25, "hello", "world"];

//JSON
[25, "hello", "world"]

[
{
“name”: "name1",
"age": 1,
"detail": {
"aaa": "aaa",
"bbb": "bbb"
}
},
{
“name”: "name2",
"age": 2,
"detail": {
"aaa": "aaa",
"bbb": "bbb"
}
},
{
“name”: "name3",
"age": 3,
"detail": {
"aaa": "aaa",
"bbb": "bbb"
}
},
{
“name”: "name4",
"age": 4,
"detail": {
"aaa": "aaa",
"bbb": "bbb"
}
},
]
  • 数据也没有变量的概念
  • 同样也没有分号

解析与序列化(javascript中的JSON对象)

1
2
3
4
5
//JSON使用
books[2].title

//XML
doc.getElementsByTagName("book")[2].getAtrribute("title")

从上也不难理解为什么JSON受开发人员欢迎。

Javascript早期解析JSON

直接使用eval()对JSON数据进行中值。这样会存在风险,因可能会执行一些恶意代码。

1
2
3
4
5
6
//前端老人可能还用过如下方式:
var json= eval("{message:(function (){ window.location='http://zh.wikipedia.org/wiki/JSON#.E5.AE.89.E5.85.A8.E6.80.A7.E5.95.8F.E9.A1.8C'; })()}");

var json= eval("(" + "{message:(function (){ window.location='http://zh.wikipedia.org/wiki/JSON#.E5.AE.89.E5.85.A8.E6.80.A7.E5.95.8F.E9.A1.8C'; })()}" + ")");

//上面解析的内容就非标准的JSON,存在恶意代码。

javascript中的JSON对象

JSON.parse()
  • 作用:解析一个JSON字符串,可选地转换生成的值及其属性,并返回值。
  • 语法

    • JSON.parse(text[, reviver])
    • 参数
      • text:要被解析成JavaScript值的字符串,查看 JSON 对象学习的JSON 语法的说明。
      • reviver [可选] 如果是一个函数,则规定了原始值如何被解析改造,在被返回之前。
    • 返回值: Object对应给定的JSON文本。
    • 异常: 若被解析的 JSON 字符串是非法的,则会抛出 一个语法错误 异常。
      1
      2
      3
      4
      5
      6
      JSON.parse('{}');              // {}
      JSON.parse('true'); // true
      JSON.parse('"foo"'); // "foo"
      JSON.parse('[1, 5, "false"]'); // [1, 5, "false"]
      JSON.parse('null'); // null
      JSON.parse('1'); // 1
  • 使用 reviver 函数
    如果指定了 reviver 函数,则解析出的 JavaScript 值(解析值)会经过一次转换后才将被最终返回(返回值)。更具体点讲就是:解析值本身以及它所包含的所有属性,会按照一定的顺序(从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身)分别的去调用 reviver 函数,在调用过程中,当前属性所属的对象会作为 this 值,当前属性名和属性值会分别作为第一个和第二个参数传入 reviver 中。如果 reviver 返回 undefined,则当前属性会从所属对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值。

当遍历到最顶层的值(解析值)时,传入 reviver 函数的参数会是空字符串 “”(因为此时已经没有真正的属性)和当前的解析值(有可能已经被修改过了),当前的 this 值会是 {“”: 修改过的解析值},在编写 reviver 函数时,要注意到这个特例。(这个函数的遍历顺序依照:从最内层开始,按照层级顺序,依次向外遍历)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JSON.parse('{"p": 5}', function (k, v) {
if(k === '') return v; // 如果到了最顶层,则直接返回属性值,
return v * 2; // 否则将属性值变为原来的 2 倍。
}); // { p: 10 }

JSON.parse('{"1": 1, "2": 2,"3": {"4": 4, "5": {"6": 6}}}', function (k, v) {
console.log(k); // 输出当前的属性名,从而得知遍历顺序是从内向外的,
// 最后一个属性名会是个空字符串。
return v; // 返回原始属性值,相当于没有传递 reviver 参数。
});

// 1
// 2
// 4
// 6
// 5
// 3
// ""
  • JSON.parse() 不允许用逗号作为结尾
    1
    2
    3
    // both will throw a SyntaxError
    JSON.parse("[1, 2, 3, 4, ]");
    JSON.parse('{"foo" : 1, }');
JSON.stringify()
  • 作用:返回与指定值相对应的一个JSON字符串,可选地仅包含某些属性或以用户定义的方式替换属性值。

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

  • 参数
    • value: 将要序列化成 一个JSON 字符串的值。
    • replacer 【可选】:如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为null或者未提供,则对象所有的属性都会被序列化;关于该参数更详细的解释和示例,请参考使用原生的 JSON 对象一文。
    • space 【可选】:指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;如果该参数为字符串(字符串的前十个字母),该字符串将被作为空格;如果该参数没有提供(或者为null)将没有空格。
  • 返回值: 一个表示给定值的JSON字符串。

  • 关于序列化,有下面五点注意事项:

    • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
    • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
    • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
    • 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
    • 不可枚举的属性会被忽略
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      JSON.stringify({});                        // '{}'
      JSON.stringify(true); // 'true'
      JSON.stringify("foo"); // '"foo"'
      JSON.stringify([1, "false", false]); // '[1,"false",false]'
      JSON.stringify({ x: 5 }); // '{"x":5}'

      JSON.stringify({x: 5, y: 6});
      // "{"x":5,"y":6}"

      JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
      // '[1,"false",false]'

      JSON.stringify({x: undefined, y: Object, z: Symbol("")});
      // '{}'

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

      JSON.stringify({[Symbol("foo")]: "foo"});
      // '{}'

      JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]);
      // '{}'

      JSON.stringify(
      {[Symbol.for("foo")]: "foo"},
      function (k, v) {
      if (typeof k === "symbol"){
      return "a symbol";
      }
      }
      );


      // undefined

      // 不可枚举的属性默认会被忽略:
      JSON.stringify(
      Object.create(
      null,
      {
      x: { value: 'x', enumerable: false },
      y: { value: 'y', enumerable: true }
      }
      )
      );

      // "{"y":"y"}"
  • replacer参数

eplacer参数可以是一个函数或者一个数组。作为函数,它有两个参数,键(key)值(value)都会被序列化。

1. 如果返回一个 Number, 转换成相应的字符串被添加入JSON字符串。
2. 如果返回一个 String, 该字符串作为属性值被添加入JSON。
3. 如果返回一个 Boolean, "true" 或者 "false"被作为属性值被添加入JSON字符串。
4. 如果返回任何其他对象,该对象递归地序列化成JSON字符串,对每个属性调用replaceer方法。除非该对象是一个函数,这种情况将不会被序列化成JSON字符串。
5. 如果返回undefined,该属性值不会在JSON字符串中输出。

注意: 不能用replacer方法,从数组中移除值(values),如若返回undefined或者一个函数,将会被null取代。

例子(function)
1
2
3
4
5
6
7
8
9
10
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}

var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
var jsonString = JSON.stringify(foo, replacer);
//JSON序列化结果为 {"week":45,"month":7}.
例子(array)
1
2
3
//如果replacer是一个数组,数组的值代表将被序列化成JSON字符串的属性名。
JSON.stringify(foo, ['week', 'month']);
// '{"week":45,"month":7}', 只保留“week”和“month”属性值。
space 参数
1
2
3
4
5
6
7
8
9
//space 参数用来控制结果字符串里面的间距。如果是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格(最多10个空格);如果是一个字符串,则每一级别会比上一级别多缩进用该字符串(或该字符串的前十个字符)。
JSON.stringify({ a: 2 }, null, " "); // '{\n "a": 2\n}'

//使用制表符(\t)来缩进:
JSON.stringify({ uno: 1, dos : 2 }, null, '\t')
// '{ \
// "uno": 1, \
// "dos": 2 \
// }'
toJSON 方法
1
2
3
4
5
6
7
8
9
//如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化,例如:
var obj = {
foo: 'foo',
toJSON: function () {
return 'bar';
}
};
JSON.stringify(obj); // '"bar"'
JSON.stringify({x: obj}); // '{"x":"bar"}'
JSON.stringify用作 JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//注意JSON不是javascript严格意义上的子集,在JSON中不需要省略两条终线(Line separator和Paragraph separator)但在JavaScript中需要被省略。因此,如果JSON被用作JSONP时,下面方法可以使用:
function jsFriendlyJSONStringify (s) {
return JSON.stringify(s).
replace(/\u2028/g, '\\u2028').
replace(/\u2029/g, '\\u2029');
}

var s = {
a: String.fromCharCode(0x2028),
b: String.fromCharCode(0x2029)
};
try {
eval('(' + JSON.stringify(s) + ')');
} catch (e) {
console.log(e); // "SyntaxError: unterminated string literal"
}

// No need for a catch
eval('(' + jsFriendlyJSONStringify(s) + ')');

// console.log in Firefox unescapes the Unicode if
// logged to console, so we use alert
alert(jsFriendlyJSONStringify(s)); // {"a":"\u2028","b":"\u2029"}
使用 JSON.stringify 结合 localStorage 的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//一些时候,你想存储用户创建的一个对象,并且,即使在浏览器被关闭后仍能恢复该对象。下面的例子是 JSON.stringify 适用于这种情形的一个样板:
// 创建一个示例数据
var session = {
'screens' : [],
'state' : true
};
session.screens.push({"name":"screenA", "width":450, "height":250});
session.screens.push({"name":"screenB", "width":650, "height":350});
session.screens.push({"name":"screenC", "width":750, "height":120});
session.screens.push({"name":"screenD", "width":250, "height":60});
session.screens.push({"name":"screenE", "width":390, "height":120});
session.screens.push({"name":"screenF", "width":1240, "height":650});

// 使用 JSON.stringify 转换为 JSON 字符串
// 然后使用 localStorage 保存在 session 名称里
localStorage.setItem('session', JSON.stringify(session));

// 然后是如何转换通过 JSON.stringify 生成的字符串,该字符串以 JSON 格式保存在 localStorage 里
var restoredSession = JSON.parse(localStorage.getItem('session'));

// 现在 restoredSession 包含了保存在 localStorage 里的对象
console.log(restoredSession);
复制对象
1
2
3
var a = {a: 123, b: 234};
var b = JSON.parse(JSON.stringify(a));
console.log(a === b); //false

Polyfill

JSON对象不被旧版本浏览器支持。你可以把下面代码放到脚本的开始位置,这样就可以在那些没有原生支持 JSON 对象的浏览器(如IE6)中使用 JSON对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if (!window.JSON) {
window.JSON = {
parse: function(sJSON) { return eval('(' + sJSON + ')'); },
stringify: (function () {
var toString = Object.prototype.toString;
var isArray = Array.isArray || function (a) { return toString.call(a) === '[object Array]'; };
var escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t'};
var escFunc = function (m) { return escMap[m] || '\\u' + (m.charCodeAt(0) + 0x10000).toString(16).substr(1); };
var escRE = /[\\"\u0000-\u001F\u2028\u2029]/g;
return function stringify(value) {
if (value == null) {
return 'null';
} else if (typeof value === 'number') {
return isFinite(value) ? value.toString() : 'null';
} else if (typeof value === 'boolean') {
return value.toString();
} else if (typeof value === 'object') {
if (typeof value.toJSON === 'function') {
return stringify(value.toJSON());
} else if (isArray(value)) {
var res = '[';
for (var i = 0; i < value.length; i++)
res += (i ? ', ' : '') + stringify(value[i]);
return res + ']';
} else if (toString.call(value) === '[object Object]') {
var tmp = [];
for (var k in value) {
if (value.hasOwnProperty(k))
tmp.push(stringify(k) + ': ' + stringify(value[k]));
}
return '{' + tmp.join(', ') + '}';
}
}
return '"' + value.toString().replace(escRE, escFunc) + '"';
};
})()
};
}

众所周知,更复杂的JSON对象 polyfills 是[clarinet]https://github.com/dscape/clarinet()
Oboe.js JSON2JSON3