JavaScript JSON

207 阅读11分钟

本文咱们来学习一下JavaScript中最常用的JSON数据格式。

一、什么是 JSON

JavaScript Object Notation (JSON) 是一种轻量级的、基于文本的、与语言无关的数据交换格式。它使用人类可读的文本来存储和传输由属性-值对和数组(或其他可序列化值)组成的数据对象。

JSON 是一种独立于语言的数据格式。它源自 JavaScript,但许多现代编程语言都包含生成和解析 JSON 格式数据的代码。 JSON 文件名使用扩展名 .json

1.1 JSON 的语法规则

(1)JSON 的格式

JSON 建立在两种结构之上:

  • 名称/值对的集合:在各种语言中,这被实现为对象、记录、结构、字典、哈希表、键控列表或关联数组。
  • 值的有序列表:在大多数语言中,这被实现为数组、向量、列表或序列。

在 JSON 中,它们采用以下形式:

  1. 对象

是一组无序键/值对,一个对象以左大括号({)开始,以右大括号(})结束。键和值之间以冒号()分隔,键/值对由逗号()分隔。例如:

{
    "key1": value1,
    "key2": value2,
    ...
}

注意:键必须是双引号"" 括起来的 字符串

  1. 数组

是值的有序集合。数组以左括号([)开始,以右括号(])结束。值由,逗号分隔。例如:

[value1, value2, ...]

(2)JSON 的值

值可以是双引号包裹的字符串、 数字truefalsenull对象数组。这些结构可以嵌套。

  • 字符串:是零个或多个 Unicode 字符的序列,用双引号括起来,使用反斜杠转义。
  • 数字:与 C 或 Java数字非常相似,只是不使用八进制和十六进制格式
  • 布尔值truefalse
  • 空值null
  • 对象:见上述
  • 数组:见上述 例如以下都是正确的JSON格式:
// 字符串
"json"

// 数字
1

// 布尔值
true
false

// 空值
null

// 对象
{
    "key1": "json",
    "key2": 1,
    "key3": true, 
    "key4": null,
    "key5": {},
    "key6": []
}

// 数组
["json", 1, false, null, {}, []]

此外,JSON值只能是上述其中一种,它们可以嵌套出现在对象或数组的结构中,但不能同时非嵌套存在多种值,例如下面的格式就是错误的:

{}[]112

有关 JSON 的详细语法规则,可见这篇文章

1.2 JSON 与 JavaScript对象

JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。

JavaScript类型JSON 的不同点
对象和数组属性名称必须是双引号括起来的字符串;最后一个属性后不能有逗号
数值禁止出现前导零;如果有小数点, 则后面至少跟着一位数字。
字符串只有有限的一些字符可能会被转义;禁止某些控制字符; Unicode 行分隔符 (U+2028)和段分隔符 (U+2029)被允许 ; 字符串必须用双引号括起来。

二、JavaScript 中的 JSON 对象

JSON 是一种语法,用来序列化对象、数组、数值、字符串、布尔值和 null 。它基于 JavaScript 语法,但与之不同:JavaScript不是JSON,JSON也不是JavaScript

但 JavaScript 中提供了一个 JSON 对象,用于 JSON 和 JavaScript对象之间的转换。

JSON对象包含两个方法: 用于解析 JavaScript Object Notation(JSON) 的 parse() 方法,以及将对象/值转换为 JSON字符串的 stringify() 方法。除了这两个方法, JSON这个对象本身并没有其他作用,也不能被调用或者作为构造函数调用

2.1 JSON.parse()

JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。

即:JSON 字符串 -> JavaScript 对象

(1)语法

JSON.parse(text[, reviver])
  • text:要被解析成 JavaScript 值的字符串,
  • reviver:(可选)转换器, 如果传入该参数(函数),可以用来修改解析生成的原始值,调用时机在 parse 函数返回之前.

返回值Object类型, 对应给定 JSON 文本的对象/值。

异常:若传入的字符串不符合 JSON 规范,则会抛出 SyntaxError 异常。

(2)示例

JSON字符串的几种正确格式

// 对象
console.log(JSON.parse('{}'));              // {}
// 布尔值
console.log(JSON.parse('true'));            // true
// 字符串(注意这里的字符串仍然需要双引号括起来)
console.log(JSON.parse('"foo"'));           // "foo"
// 数值
console.log(JSON.parse('1'));        // 1
// 数组
console.log(JSON.parse('[1, 5, "false"]')); // [1, 5, "false"]
console.log(JSON.parse('null'));            // null

字符串不符合 JSON 规范,抛出 SyntaxError

// 对象的最后一个键值对后加了逗号
JSON.parse('{"key1": 1, "key2": 2,}')  // SyntaxError: Unexpected token } in JSON 

// 数值的最后一个值后面加了逗号
JSON.parse('[1, 2, 3,]');   // SyntaxError: Unexpected token ] in JSON

// 对象的键没有使用双引号括起来
JSON.parse(`{'key': 1}`);  // SyntaxError: Unexpected token ' in JSON

// 对象的键不是字符串
JSON.parse('{1: 1}')  // SyntaxError: Unexpected number in JSON
JSON.parse('{true: 1}');  // SyntaxError: Unexpected token t in JSON

// 存在不止一种值
JSON.parse('{}ab');  // SyntaxError: Unexpected token t in JSON

// 字符串未用双引号括起来
JSON.parse("a");   // SyntaxError: Unexpected token a in JSON
JSON.parse('a');   // SyntaxError: Unexpected token a in JSON
// 正确格式为:JSON.parse('"a"'); 

....

传入 reviver 参数

如果指定了 reviver 函数,则解析出的 JavaScript 值(解析值)会经过一次转换后才将被最终返回(返回值)

解析值本身以及它所包含的所有属性,会按照一定的顺序(从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身)分别的去调用 reviver 函数,在调用过程中,当前属性所属的对象会作为 this 值,

当前属性名和属性值会分别作为第一个和第二个参数传入 reviver 中。

  • 如果 reviver 返回 undefined,则当前属性会从所属对象中删除
  • 如果返回了其他值,则返回的值会成为当前属性新的属性值。

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

  • reviver 函数中的参数
// 字符串
let obj = JSON.parse('"abc"', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
}); 
// k:  v: abc

// 数值
let obj = JSON.parse('1', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
}); 
// k:  v: 1

// 布尔值
let obj = JSON.parse('false', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
}); 
// k:  v: false

// null
let obj = JSON.parse('null', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
}); 
// k:  v: null

// 对象
let obj = JSON.parse('{"key1": 1, "key2": 2}', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
}); 
// k: key1 v: 1
// k: key2 v: 2
// k:  v: { key1: 1, key2: 2 }

// 数组
let obj = JSON.parse('[1, 2]', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
}); 
// k: 0 v: 1
// k: 1 v: 2
// k:  v: [ 1, 2 ]

由上述代码可以看出字符串、数值、布尔值、null,只有 {"": 解析值本身} 这一项会去调用reviver 函数。对象和数组会按指定顺序调用 reviver 函数,数组的键元素的索引下标,值是元素的值。

  • 判断是否遍历到顶层的值 当遍历到最顶层的值(解析值)时,传入 reviver 函数的参数会是空字符串 ""和当前的解析值。
let obj = JSON.parse('{"key1": 5}', function (k, v) {
    // 遍历到顶层值时,键为空字符串 ""
    if (k === '')
        return v;
    return v * 2;
}); 
console.log(obj)  // { key1: 10 }

但是字符串、数值、布尔值、null 只会遍历一项就是{"": 解析值本身},即最顶层的值。

let obj = JSON.parse('5', function (k, v) {
    // 不需要判断,因为只会遍历一项顶层的值
    return v * 2;
}); 
console.log(obj)  // 10 
  • 遍历的顺序 —— 由左向右,由内向外
JSON.parse('{"1": 1, "2": 2,"3": {"4": 4, "5": {"6": 6}}}', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
});
// k: 1 v: 1
// k: 2 v: 2
// k: 4 v: 4
// k: 6 v: 6
// k: 5 v: { '6': 6 }
// k: 3 v: { '4': 4, '5': { '6': 6 } }
// k:  v: { '1': 1, '2': 2, '3': { '4': 4, '5': { '6': 6 } } }


JSON.parse('[1, {"key1": {"key2": 2}}, 3]', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
});
// k: 0 v: 1
// k: key2 v: 2
// k: key1 v: { key2: 2 }
// k: 1 v: { key1: { key2: 2 } }
// k: 2 v: 3
// k:  v: [ 1, { key1: { key2: 2 } }, 3 ]
  • 如果某个键对应的值是对象,那么遍历到该键时,对应的值为 {key: 修改后的值}
JSON.parse('{"outer": {"inner": 2}}', function (k, v) {
    console.log("k:", k, "v:", v);
    return v;
});
// k: inner v: 2
// k: outer v: { inner: 2 }
// k:  v: { outer: { inner: 2 } }

JSON.parse('{"outer": {"inner": 2}}', function (k, v) {
    console.log("k:", k, "v:", v);
    return null;
});
// k: inner v: 2
// k: outer v: { inner: null }
// k:  v: { outer: null }JSON.parse('{"outer": {"inner": 2}}', function (k, v) {
    console.log("k:", k, "v:", v);
    return null;
});
// k: inner v: 2
// k: outer v: { inner: null }

2.2 JSON.stringify()

SON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串

即:JavaScript 对象 -> JSON 字符串

(1)语法

JSON.stringify(value[, replacer [, space]])
  • value:将要序列化成 一个 JSON 字符串的值。
  • replacer(可选):
    • 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
    • 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
    • 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
  • space(可选):指定缩进用的空白字符串,用于美化输出(pretty-print);
    • 如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;
    • 如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;
    • 如果该参数没有提供(或者为 null),将没有空格。

返回值:一个表示给定值的JSON字符串。

异常

  • 当在循环引用时会抛出异常TypeError("cyclic object value")(循环对象值)
let a = {}
let b = {a}
a.b = b;

JSON.stringify(a)  // TypeError: Converting circular structure to JSON
  • 当尝试去转换 BigInt类型的值会抛出TypeError("BigInt value can't be serialized in JSON")(BigInt值不能JSON序列化)
JSON.stringify(1n)  // TypeError: Do not know how to serialize a BigInt

(2)转换规则

  • 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。
var obj = {
  foo: 'foo',
  toJSON: function () {
    return 'bar';
  }
};
JSON.stringify(obj);      // '"bar"'
JSON.stringify({x: obj}); // '{"x":"bar"}'
  • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
  • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
JSON.stringify([new Number(1), new String("abc"), new Boolean(false)])  // [1,"abc",false]
  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined).
JSON.stringify({x: undefined, y: function(){}, z: Symbol("")})  // {}
JSON.stringify([undefined, function(){}, Symbol("")])  // [null,null,null]

JSON.stringify(undefined)  // undefined
JSON.stringify(function(){})  // undefined
  • 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
JSON.stringify({[Symbol("foo")]: "foo"});
// '{}'

JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]);
// '{}'
  • Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
  • NaN 和 Infinity 格式的数值及 null 都会被当做 null。
JSON.stringify([null, NaN, Infinity])  // [null,null,null]
  • 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
// 不可枚举的属性默认会被忽略:
JSON.stringify(
    Object.create(
        null,
        {
            x: { value: 'x', enumerable: false },
            y: { value: 'y', enumerable: true }
        }
    )
);

(3)replacer 参数的使用

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

在开始时, replacer 函数会被传入一个空字符串作为 key 值,代表着要被 stringify 的这个对象。随后每个对象或数组上的属性会被依次传入。 

函数应当返回JSON字符串中的value, 如下所示:

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

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

replacer 是一个函数

function replacer(key, value) {
    if (typeof value === "string") {
      return undefined;
    }
    
    return value;
  }
  
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: {a: 3}, month: 7};
var jsonString = JSON.stringify(foo, replacer);
console.log(jsonString);  // {"week":45,"month":7}

递归顺序 —— 由外向内

// 遍历顺序是由外向内的
function replacer(key, value) {
    console.log('key:', key, 'value:', value);
    return value;
  }
var foo = {key1: 1, key2: {key3: 3, key4: {key5: 5}}};
var jsonString = JSON.stringify(foo, replacer);

// key:  value: { key1: 1, key2: { key3: 3, key4: { key5: 5 } } }
// key: key1 value: 1
// key: key2 value: { key3: 3, key4: { key5: 5 } }
// key: key3 value: 3
// key: key4 value: { key5: 5 }
// key: key5 value: 5

replacer 是一个数组

JSON.stringify(foo, ['week', 'month']);
// '{"week":45,"month":7}', 只保留 “week” 和 “month” 属性值。

(4)space 参数的使用

  • space是数字
JSON.stringify({ a: 2, b: 3 }, null, 5);   
/*
{
    "a": 2,
    "b": 3
}
*/
  • space 是字符串
JSON.stringify({ a: 2, b: 3 }, null, 'abcd');   
/*
{
abcd"a": 2,
abcd"b": 3
}
*/