querystring 的坑
记得上次博客改版的时候遇到过一个问题,感觉数据从前端 post 到服务端后发生了不正常的改变,当时也没有多想,写了几行 hack 解决了。幸亏当时留下了大量的注释,否则今天再看见那段 hack 肯定摸不着头脑:
req.body = querystring.parse(postData.join(''));
postData = null;
/**
* 此处有坑:
* 1.若前端传过来的value是个数组,则key会加'[]'后缀
* 如,前端post过来的data是{arr: [1,2,3]},则实际接收时,key是'arr[]'
* 2.若传过来的数组中只有一个元素,实际接收到的value是这个元素
* 如,前端post过来的data是{arr: [1]},则实际接收时,value是1
*/
for(var i in req.body){
// 命中1
if(/^.+\[\]$/.test(i)){
// 命中2
if(req.body[i].constructor != Array){
req.body[i] = [req.body[i]];
}
req.body[i.replace('[]', '')] = req.body[i];
delete req.body[i];
}
}
现在再看看这段代码,绝对是「基于巧合的编程」的典范啊,完全不知道 json 的序列化,如果接收的数据碰上是多个对象组成的数组或者多维数组,绝对挂的体无完肤啊,好在前端也是自己写,没有传递更复杂的数据,「恰好」没有发生问题。
但即使是传递的数据被序列化了,好像也不符合大多数前端库封装的 ajax 中对 json 序列化的规矩啊,如 jQuery 和 Zepto, 他们分别都是使用了 $.param 这个方法对传递的数据做处理:
//示例代码
var $ = require('jQuery');//或者Zepto
//传递一维对象
var simple = {a: 1, b: 2};
$.param(simple);
//得到
//a=1&b=2
//传递二维或多维
var complex = {
a: [1, 2, 3],
b: [
{
c: 4,
d: 5,
e: 6
},
{
f: 7,
g: 8,
h: 9
}
],
c: {
d: 10
}
};
$.param(complex);
//将结果decodeURIComponent之后得到:
//a[]=1&a[]=2&a[]=3&b[0][c]=4&b[0][d]=5&b[0][e]=6&b[1][f]=7&b[1][g]=8&b[1][h]=9&c[d]=10
上面的结果大致可以看出 json 序列化的规律,其实我对其他语言都能约定俗成的使用同一规则表示惊奇。但回过头看看我大 node:
var querystring = require('querystring');
//传递一维对象序列化后的字符串
var simple = 'a=1&b=2';
querystring.parse(simple);
//得到
//{a: '1', b: '2'}
//传递二维或多维对象序列化后的字符串
var complex = `a[]=1&a[]=2&a[]=3&b[0][c]=4&b[0][d]=5&b[0][e]=6&b[1][f]=7&b[1][g]=8&b[1][h]=9&c[d]=10`;
querystring.parse(complex);
// 得到:
// {
// 'a[]': ['1', '2', '3'],
// 'b[0][c]': '4',
// 'b[0][d]': '5',
// 'b[0][e]': '6',
// 'b[1][f]': '7',
// 'b[1][g]': '8',
// 'b[1][h]': '9',
// 'c[d]': '10'
// }
//并且
querystring.parse('a[]=1');
//得到
//{
// 'a[]': 1
//}
//还有url对象的url.parse(urlStr, true),
//官网明确说明解析query也使用了querystring.parse
虽然说 querystring 可能不是标准的用于反序列化模块,但 url 模块的 parse 函数,官网给出的文档上写着如果第二个参数传入 true 的话,最终 url.parse 得出来对象的 query 属性,将是一个解析了 search 的对象,并且是用 querystring 来解析的:
var url = require('url');
url.parse('http://localhost:3000/user?a[]=1&a[]=2&b[]=3#4', true);
// 将得到:
// {
// protocol: 'http:',
// slashes: true,
// auth: null,
// host: 'localhost:3000',
// port: '3000',
// hostname: 'localhost',
// hash: '#4',
// search: '?a[]=1&a[]=2&b[]=3',
// query: { 'a[]': [ '1', '2' ], 'b[]': '3' },
// pathname: '/user',
// path: '/user?a[]=1&a[]=2&b[]=3',
// href: 'http://localhost:3000/user?a[]=1&a[]=2&b[]=3#4'
// }
也就是说,只有当前端传过来的数据是一维对象序列化的结果,才能被正确的解出来。所以才有了前面的笑话…
目前还没有发现 node 的哪个自带模块可以正常的反序列化,第三方的倒是有不少,推荐 qs
测试 node 版本:
v0.10.35
v4.0.0
v4.1.1