有时候我们需要将Javascript对象转换为string用于网络传输,通常转换为字符串我们会手动实现toString()方法,例如:

let user = {
  name: "John",
  age: 30,

  toString() {
    return `{name: "${this.name}", age: ${this.age}}`;
  }
};

alert(user); // {name: "John", age: 30}

但是如果这样写的话会出现麻烦,比如我们添加或移除属性的时候就要重写toString方法,这就很是头疼。因此,Javascript提供了JSON机制来将对象转换为字符串,也就是JSON.stringify()


JSON.stringify()

案例

JSON.stringify()返回对象的json格式,也就是字符串的格式,例如:

et student = {
  name: 'John',
  age: 30,
  isAdmin: false,
  courses: ['html', 'css', 'js'],
  wife: null
};

let json = JSON.stringify(student);

alert(typeof json); // we've got a string!

alert(json);
/* JSON-encoded object:
{
  "name": "John",
  "age": 30,
  "isAdmin": false,
  "courses": ["html", "css", "js"],
  "wife": null
}
*/

要注意的是json格式必须使用双引号,不能是单引号或则反引号


原型的JSON转换

JSON除了转换对象外,也可以应用于原型类型,例如:

// a number in JSON is just a number
alert( JSON.stringify(1) ) // 1

// a string in JSON is still a string, but double-quoted
alert( JSON.stringify('test') ) // "test"

alert( JSON.stringify(true) ); // true

alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]




默认忽略的属性

JSON.stringify()在转换对象的时候会默认忽略以下属性:

(1)方法属性,如function;

(2)Symbol属性;

(3)值为undefined的属性;

例如:

let user = {
  sayHi() { // ignored
    alert("Hello");
  },
  [Symbol("id")]: 123, // ignored
  something: undefined // ignored
};

alert( JSON.stringify(user) ); // {} (empty object)


嵌套对象的JSON转换

当需要转换的对象中嵌套了其他的对象或者数组,JSON同样也支持转换,例如:

let meetup = {
  title: "Conference",
  room: {
    number: 123,
    participants: ["john", "ann"]
  }
};

alert( JSON.stringify(meetup) );
/* The whole structure is stringified:
{
  "title":"Conference",
  "room":{"number":23,"participants":["john","ann"]},
}
*/


循环引用的问题

这里有非常重要的一点是,被转换的对象不能出现循环引用,例如:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: ["john", "ann"]
};

meetup.place = room;       // meetup references room
room.occupiedBy = meetup; // room references meetup

JSON.stringify(meetup); // Error: Converting circular structure to JSON

这里因为meetu.place引用了room,而room.occupiedBy引用了meetup,存在循环引用问题,故报错。



JSON.stringify()其余的两个参数

JSON.stringify()具体的语法如下:

let json = JSON.stringify(value[, replacer, space])


第一个参数我们都知道了,现在来讲解第二个参数replacer。replacer参数可以是一个数组或者是一个函数function(key,value),它表示需要转换为json的属性名,例如:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup references room
};

room.occupiedBy = meetup; // room references meetup

alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}

这里如果不使用replacer参数指定要转换的属性的话,则会出现循环引用的错误,但由于我们指定了title和participants两个属性,故stringify()就只转换这两个属性。需要注意的是此时participants的值为空,如果想不为空就必须把name属性名也指定,例如下面这个例子:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup references room
};

room.occupiedBy = meetup; // room references meetup

alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
  "title":"Conference",
  "participants":[{"name":"John"},{"name":"Alice"}],
  "place":{"number":23}
}
*/



以数组的形式来指定属性非常繁琐,如果是一个复杂对象的话就要写一大串属性名,那真是头疼。幸运的是,我们还可以使用隐射函数function(key,value)来指定属性名,例如:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup references room
};

room.occupiedBy = meetup; // room references meetup

alert( JSON.stringify(meetup, function replacer(key, value) {
  alert(`${key}: ${value}`); // to see what replacer gets
  return (key == 'occupiedBy') ? undefined : value;
}));

/* key:value pairs that come to replacer:
:             [object Object]
title:        Conference
participants: [object Object],[object Object]
0:            [object Object]
name:         John
1:            [object Object]
name:         Alice
place:        [object Object]
number:       23
*/

stringify()遍历meetup所有键值对的时候会调用function replacer(key,value),并根据该函数返回的属性值来确定要转换的属性,当replacer(key,value)返回undefined的时候,就说明该属性不需要被转换,因为stringify()不会转换值为undefined的属性



现在我们来讲解第三个属性space,space用来指定嵌套对象的属性前面的空格数量,它是转换后的字符串格式更好看,看下面例子就明白了:

let user = {
  name: "John",
  age: 25,
  roles: {
    isAdmin: false,
    isEditor: true
  }
};

alert(JSON.stringify(user, null, 2));
/* two-space indents:
{
  "name": "John",
  "age": 25,
  "roles": {
    "isAdmin": false,
    "isEditor": true
  }
}
*/

/* for JSON.stringify(user, null, 4) the result would be more indented:
{
    "name": "John",
    "age": 25,
    "roles": {
        "isAdmin": false,
        "isEditor": true
    }
}
*/



自定义toJSON()方法

就像自定义toString()方法一样,我们同样可以定义toJSON()方法,stringify()内部会默认调用它,例如:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  date: new Date(Date.UTC(2017, 0, 1)),
  room
};

alert( JSON.stringify(meetup) );
/*
  {
    "title":"Conference",
    "date":"2017-01-01T00:00:00.000Z",  // (1)
    "room": {"number":23}               // (2)
  }
*/

上述例子中date属性的值变成了字符串,那是因为Date对象内部实现了toJSON()方法来放回字符串格式的date对象内容。


对于普通对象,我们可以手动来实现toJSON()方法,例如:

let room = {
  number: 23,
  toJSON() {
    return this.number;
  }
};

let meetup = {
  title: "Conference",
  room
};

alert( JSON.stringify(room) ); // 23

alert( JSON.stringify(meetup) );
/*
  {
    "title":"Conference",
    "room": 23
  }
*/



JSON.parse()

JSON.parse()是JSON.stringify()的逆过程,用来解析json字符串并转换为对应的对象,它的具体语法如下:

let value = JSON.parse(str[, reviver]);

其中str是要被解析的json格式字符串,revier是可选的函数参数function(key,value),更stringify()的replacer参数很像,看下面例子:

// stringified array
let numbers = "[0, 1, 2, 3]";

numbers = JSON.parse(numbers);

alert( numbers[1] ); // 1



嵌套对象如下例子:

let user = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';

user = JSON.parse(user);

alert( user.friends[1] ); // 1



这里介绍第二个参数reviver,parse()解析每个键值对的时候会自动调用revier函数来确定是否解析此键值对,这里有一个报错的例子:

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str);

alert( meetup.date.getDate() ); // Error!

显然,JSON.parse()不知道date属性需要转换为Date对象,所有我们要实现revier函数参数来告诉它,例如:

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str, function(key, value) {
  if (key == 'date') return new Date(value);
  return value;
});

alert( meetup.date.getDate() ); // now works!



对于嵌套对象也一样,例如:

let schedule = `{
  "meetups": [
    {"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
    {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
  ]
}`;

schedule = JSON.parse(schedule, function(key, value) {
  if (key == 'date') return new Date(value);
  return value;
});

alert( schedule.meetups[1].date.getDate() ); // works!