你可能不知道的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中不等于的代码是什么怎么写
Dec 29 Javascript
node.js中的fs.writeSync方法使用说明
Dec 15 Javascript
js判断某个方法是否存在实例代码
Jan 10 Javascript
JS组件Bootstrap Table表格多行拖拽效果实现代码
Dec 08 Javascript
javascript trie前缀树的示例
Jan 29 Javascript
js删除数组中的元素delete和splice的区别详解
Feb 03 Javascript
JavaScript 中的12种循环遍历方法【总结】
May 31 Javascript
基于vue-cli npm run build之后vendor.js文件过大的解决方法
Sep 27 Javascript
jQuery实现数字自动增加或者减少的动画效果示例
Dec 11 jQuery
判断JavaScript中的两个变量是否相等的操作符
Dec 21 Javascript
javascript设计模式 ? 原型模式原理与应用实例分析
Apr 10 Javascript
vue 数字翻牌器动态加载数据
Apr 20 Vue.js
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全概率运算函数(优化版) Webgame开发必备
2011/07/04 PHP
php长字符串定义方法
2012/07/12 PHP
php根据日期显示所在星座的方法
2015/07/13 PHP
Thinkphp3.2实用篇之计算型验证码示例
2017/02/09 PHP
详解Yaf框架PHPUnit集成测试方法
2017/12/27 PHP
laravel 解决多库下的DB::transaction()事务失效问题
2019/10/21 PHP
图片自动缩小的js代码,用以防止图片撑破页面
2007/03/12 Javascript
return false,对阻止事件默认动作的一些测试代码
2010/11/17 Javascript
jQuery中绑定事件的命名空间详解
2011/04/05 Javascript
你必须知道的JavaScript 中字符串连接的性能的一些问题
2013/05/07 Javascript
JQuery入门基础小实例(1)
2015/09/17 Javascript
JS封装cookie操作函数实例(设置、读取、删除)
2015/11/17 Javascript
js中的触发事件对象event.srcElement与event.target详解
2017/03/15 Javascript
js制作简单的音乐播放器的示例代码
2017/08/28 Javascript
Vue添加请求拦截器及vue-resource 拦截器使用
2017/11/23 Javascript
Vue实现web分页组件详解
2017/11/28 Javascript
使用webpack搭建vue项目实现脚手架功能
2019/03/15 Javascript
微信小程序自定义toast组件的方法详解【含动画】
2019/05/11 Javascript
微信小程序非跳转式组件授权登录的方法示例
2019/05/22 Javascript
利用node 判断打开的是文件 还是 文件夹的实例
2019/06/10 Javascript
用Python写飞机大战游戏之pygame入门(4):获取鼠标的位置及运动
2015/11/05 Python
详解windows python3.7安装numpy问题的解决方法
2018/08/13 Python
Python简直是万能的,这5大主要用途你一定要知道!(推荐)
2019/04/03 Python
解决Tensorboard可视化错误:不显示数据 No scalar data was found
2020/02/15 Python
屏蔽Django admin界面添加按钮的操作
2020/03/11 Python
CSS3文本换行word-wrap解决英文文本超过固定宽度不换行
2013/10/10 HTML / CSS
深入浅析css3 中display box使用方法
2015/11/25 HTML / CSS
JAVA中运算符的分类及举例
2015/09/12 面试题
播音主持女孩的自我评价分享
2013/11/20 职场文书
八年级英语教学反思
2014/01/09 职场文书
继承公证书
2014/04/09 职场文书
乐观自信演讲稿范文
2014/05/21 职场文书
应届毕业生求职简历自我评价
2015/03/02 职场文书
学校节水倡议书
2015/04/29 职场文书
乡镇安全生产月活动总结
2015/05/08 职场文书
网络营销实训总结
2015/08/03 职场文书