你可能不知道的JSON.stringify()详解


Posted in Javascript onAugust 17, 2017

前言

JSON已经逐渐替代XML被全世界的开发者广泛使用。本文深入讲解JavaScript中使用JSON.stringify的一些细节问题。首先简单回顾一下JSON和JavaScript:

  • 不是所有的合法的JSON都是有效的JavaScript;
  • JSON只是一个文本格式;
  • JSON中的数字是十进制。

1. JSON.stringify

let foo = { a: 2, b: function() {} };
JSON.stringify(foo);
// "{ "a": 2 }"

JSON.stringify函数将一个JavaScript对象转换成文本化的JSON。不能被文本化的属性会被忽略。foo中属性b的值是函数定义,没有被转换而丢失。

还有哪些属性也不能转换?

1. 循环引用

如果一个对象的属性值通过某种间接的方式指回该对象本身,那么就是一个循环引用。比如:

var bar = {
 a: {
  c: foo
 }
};
var foo = {
 b: bar
};

属性c指向自己,如果层层解析,将会进入一个无限循环。我们尝试将其打印出来看看:

let fooStringified = JSON.stringify(foo);
console.log(fooStringified); // {"b":{"a":{}}}

c的属性指向foo对象,foo对象中的b属性又指向bar对象而无法处理,整个被忽略而返回空对象。

如下定义(原文中的例子)是无法通过编译的:

let foo = {b : foo};

错误信息:

ReferenceError: foo is not defined
 at repl:1:14

在函数式语言Haskell中,因为有Lazy Evaluation技术,可以使用类似的定义方法。

2. Symbol和undefined

let foo = { b: undefined };
JSON.stringify(foo);
// {}
// Symbols
foo.b = Symbol();
JSON.stringify(foo);
// {}

例外情况

在数组中,不可被stringify的元素用null填充。

let foo = [Symbol(), undefined, function() {}, 'works']
JSON.stringify(foo);
// "[null,null,null,'works']"

这样可以保持数组本身的“形状”,也就是每一个元素原本的索引。

为什么有些属性无法被stringify呢?

因为JSON是一个通用的文本格式,和语言无关。设想如果将函数定义也stringify的话,如何判断是哪种语言,并且通过合适的方式将其呈现出来将会变得特别复杂。特别是和语言相关的一些特性,比如JavaScript中的Symbol。

ECMASCript官方也特意强调了这一点:

It does not attempt to impose ECMAScript's internal data representations on other programming languages. Instead, it shares a small subset of ECMAScript's textual representations with all other programming languages.

2. 重写对象toJSON函数

一个绕过对象某些属性无法stringify的方法就是实现对象的toJSON方法来自定义被stringify的对象。因为几乎每一个AJAX调用都会使用JSON.stringify,掌握该技巧将会对处理服务器交互有很大帮助。

和toString允许你将对象中的元素以字符串(string)的形式返回类似,toJSON提供了一种可以将对象中不能stringify的属性转换的方法,使得接下来调用的JSON.stringify可以将其转换成JSON格式。

function Person (first, last) {
 this.firstName = first;
 this.last = last;
}
 
Person.prototype.process = function () {
 return this.firstName + ' ' +
   this.lastName;
};
 
let ade = new Person('Ade', 'P');
JSON.stringify(ade);
// "{"firstName":"Ade","last":"P"}"

Person实例ade的process函数没有被stringify。假想如果服务器只想要ade的全称,而不是分别获取姓和名,我们可以直接定义toJSON来达到目的:

Person.prototype.toJSON = function () {
 return { fullName: this.process(); };
};
 
let ade = new Person('Ade', 'P');
JSON.stringify(ade);
// "{"fullName":"Ade P"}"

定义toJSON的优点是复用性和稳定性,你可以将ade配合任何库使用,传输的数据都将是你通过toJSON定义而返回的fullName。

// jQuery
$.post('endpoint', ade);
 
// Angular 2
this.httpService.post('endpoint', ade)

3. 可选参数

JSON.stringify完整的定义如下:

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

replacer和space都是可选参数,接下来我们来分别讲解。

Replacer

replacer是一个过滤函数或则一个数组包含要被stringify的属性名。如果没有定义,默认所有属性都被stringify。

1. 数组

只有在数组中的属性被stringify:

let foo = {
 a : 1,
 b : "string",
 c : false
};
JSON.stringify(foo, ['a', 'b']);
//"{"a":1,"b":"string"}"

嵌套属性也同样会被过滤:

let bar = {
 a : 1,
 b : { c : 2 }
};
JSON.stringify(bar, ['a', 'b']);
//"{"a":1,"b":{}}"
 
JSON.stringify(bar, ['a', 'b', 'c']);
//"{"a":1,"b":{"c":2}}"

定义过滤数组有时候并不能满足需求,那么可以自定义过滤函数。

2. 函数

过滤函数以对象中的每一个属性和值作为输入,返回值有以下几种情况:

  • 返回undefined表示忽略该属性;
  • 返回字符串,布尔值或则数字将会被stringify;
  • 返回对象将会触发递归调用知道遇到基本类型的属性;
  • 返回无法stringify的值将会被忽略;
let baz = {
 a : 1,
 b : { c : 2 }
};
 
// 返回大于1的值
let replacer = function (key, value) {
 if(typeof === 'number') {
  return value > 1 ? value: undefined;
 }
 return value;
};
 
JSON.stringify(baz, replacer);
// "{"b":{"c":2}}"

通过改写上面的函数加入适当的输出,可以看到具体的执行步骤:

let obj = {
 a : 1,
 b : { c : 2 }
};
 
let tracer = function (key, value){
 console.log('Key: ', key);
 console.log('Value: ', value);
 return value;
};
 
JSON.stringify(obj, tracer);
// Key:
// Value: Object {a: 1, b: Object}
// Key: a
// Value: 1
// Key: b
// Value: Object {c: 2}
// Key: c
// Value: 2

space

你是否意识到调用默认的JSON.stringify返回的值只要一行,而且完全没有空格?如果想要更加美观的打印出来,那么就需要使用space这个参数了。

我告诉你一个非常简单的方法:通过tab(‘\t')来分割即可。

let space = {
 a : 1,
 b : { c : 2 }
};
 
// 使用制表符
JSON.stringify(space, undefined, '\t');
// "{
// "a": 1,
// "b": {
// "c": 2
// }
// }"
 
JSON.stringify(space, undefined, '');
// {"a":1,"b":{"c":2}}
 
// 自定义分隔符
JSON.stringify(space, undefined, 'a');
// "{
// a"a": 1,
// a"b": {
// aa"c": 2
// a}
// }"

一道三颗星的思考题:为什么打印结果的倒数第三行有两个a呢?

结论

本文介绍了一些使用toJSON的技巧:

  • 无法stringify的几种类型
  • 使用toJSON来自定义JSON.stringify的属性
  • 可选参数replacer的两种定义方法来过滤属性
  • 可选参数space用来格式化输出结果
  • 数组和对象中如果包含无法stringify的元素的时候的区别

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

原文: What you didn't know about JSON.Stringify

译者: Fundebug

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

Javascript 相关文章推荐
Javascript实现真实字符串剩余字数提示的实例代码
Oct 22 Javascript
JavaScritp添加url参数并将参数加入到url中及更改url参数的方法
Oct 26 Javascript
JS判断输入字符串长度实例代码(汉字算两个字符,字母数字算一个)
Aug 02 Javascript
AngularJS实现与Java Web服务器交互操作示例【附demo源码下载】
Nov 02 Javascript
利用iscroll4实现轮播图效果实例代码
Jan 11 Javascript
原生JS控制多个滚动条同步跟随滚动效果
Dec 22 Javascript
详解Ubuntu安装angular-cli遇到的坑
Sep 08 Javascript
vue动态设置img的src路径实例
Sep 18 Javascript
Vue实现本地购物车功能
Dec 05 Javascript
javascript对HTML字符转义与反转义
Dec 13 Javascript
使用element-ui table expand展开行实现手风琴效果
Mar 15 Javascript
Vue extend的基本用法(实例详解)
Dec 09 Javascript
vue组件Prop传递数据的实现示例
Aug 17 #Javascript
js实现随机点名小功能
Aug 17 #Javascript
浅谈Vuejs Prop基本用法
Aug 17 #Javascript
简单快速的实现js计算器功能
Aug 17 #Javascript
使用jquery的jsonp如何发起跨域请求及其原理详解
Aug 17 #jQuery
如何理解Vue的.sync修饰符的使用
Aug 17 #Javascript
浅谈JS中的反柯里化( uncurrying)
Aug 17 #Javascript
You might like
PHP操作FTP类 (上传、下载、移动、创建等)
2016/03/31 PHP
修改yii2.0用户登录使用的user表为其它的表实现方法(推荐)
2017/08/01 PHP
测试JavaScript字符串处理性能的代码
2009/12/07 Javascript
javascript function调用时的参数检测常用办法
2010/02/26 Javascript
jQuery ajax dataType值为text json探索分享
2013/09/23 Javascript
jquery禁止输入数字以外的字符的示例(纯数字验证码)
2014/04/10 Javascript
JS数组的常见用法实例
2015/02/10 Javascript
JavaScript动态加载样式表的方法
2015/03/21 Javascript
JavaScipt中栈的实现方法
2016/02/17 Javascript
WordPress 单页面上一页下一页的实现方法【附代码】
2016/03/10 Javascript
jQuery对table表格进行增删改查
2020/12/22 Javascript
JavaScript设计模式之单例模式原理与用法实例分析
2018/07/26 Javascript
vue项目环境变量配置的实现方法
2018/10/12 Javascript
微信小程序HTTP接口请求封装代码实例
2019/09/05 Javascript
javascript中contains是否包含功能实现代码(扩展字符、数组、dom)
2020/04/07 Javascript
Vue 数据绑定的原理分析
2020/11/16 Javascript
[01:50]WODOTA制作 DOTA2中文宣传片《HERO》
2013/04/28 DOTA
[02:53]DOTA2英雄昆卡基础教程
2013/11/25 DOTA
[04:07]显微镜下的DOTA2第八期——英雄复活动作
2014/06/24 DOTA
[01:10]DOTA2英雄背景故事第四期之混沌法则混沌骑士
2020/07/16 DOTA
python连接mongodb密码认证实例
2018/10/16 Python
Python多线程原理与用法实例剖析
2019/01/22 Python
Python3获取拉勾网招聘信息的方法实例
2019/04/03 Python
使用Python制作表情包实现换脸功能
2019/07/19 Python
Python递归函数 二分查找算法实现解析
2019/08/12 Python
CSS3制作轮播图的一种方法
2019/11/11 HTML / CSS
Html5实现用户注册自动校验功能实例代码
2016/05/24 HTML / CSS
Linux如何命名文件--使用文件名时应注意
2012/01/22 面试题
生产车间主任的个人自我鉴定
2013/10/25 职场文书
品质管理部岗位职责范文
2014/03/01 职场文书
寄语是什么意思
2014/04/10 职场文书
八年级英语教学计划
2015/01/23 职场文书
补充协议书
2015/01/28 职场文书
解除租赁合同协议书
2016/03/21 职场文书
nginx的zabbix 5.0安装部署的方法步骤
2021/07/16 Servers
详解Redis的三种常用的缓存读写策略步骤
2022/05/06 Redis