作者:高天阳
邮箱:13683265113@163.com
Copy 更改历史
* 2018-09-12 高天阳 初始化文档
1 简介
qs 是一个增加了一些安全性的查询字符串解析和序列化字符串的库。
主要维护者:Jordan Harband
在QS模块最初创建和维护TJ Holowaychuk 。
2 安装与使用
2.1 安装
2.1.1 npm
qs是一个npm仓库所管理的包,可通过npm命令进行安装.
2.1.2 script引入
qs是一个npm仓库所管理的包,可通过npm命令进行安装.
Copy <script src="https://cdn.bootcss.com/qs/6.5.1/qs.min.js"></script>
qs不同版本bootcdn链接
2.2 使用
Copy var qs = require('qs');
var assert = require('assert');
var obj = qs.parse('a=c');
assert.deepEqual(obj, { a: 'c' });
var str = qs.stringify(obj);
assert.equal(str, 'a=c');
2.2.1 解析对象
Copy qs.parse(string, [options]);
qs允许您通过用方括号括起子键的名称来在查询字符串中创建嵌套对象[]。例如,字符串foo[bar]=baz
转换为:
Copy assert.deepEqual(qs.parse('foo[bar]=baz'), {
foo: {
bar: 'baz'
}
});
使用该plainObjects
选项时,解析后的值将作为空对象返回,通过Object.create(null)
这样创建, 因此您应该知道原型方法不会存在,并且用户可以将这些名称设置为他们喜欢的任何值:
Copy var nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true });
assert.deepEqual(nullObject, { a: { hasOwnProperty: 'b' } });
默认情况下,如果您希望保持这些字段中的数据plainObjects
如上所述使用,或者设置allowPrototypes
为true
允许用户输入覆盖这些属性, 则会忽略将覆盖对象原型上的属性的参数。警告启用此选项通常是一个坏主意,因为在尝试使用已覆盖的属性时可能会导致问题。请务必小心此选项。
Copy var protoObject = qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true });
assert.deepEqual(protoObject, { a: { hasOwnProperty: 'b' } });
URI编码的字符串也有效:
Copy assert.deepEqual(qs.parse('a%5Bb%5D=c'), {
a: { b: 'c' }
});
您还可以嵌套对象,例如foo[bar][baz]=foobarbaz
:
Copy assert.deepEqual(qs.parse('foo[bar][baz]=foobarbaz'), {
foo: {
bar: {
baz: 'foobarbaz'
}
}
});
默认情况下,嵌套对象时,qs最多只能解析5个子项。这意味着如果您尝试解析一个字符串, 就像a[b][c][d][e][f][g][h][i]=j
您生成的对象一样 :
Copy var expected = {
a: {
b: {
c: {
d: {
e: {
f: {
'[g][h][i]': 'j'
}
}
}
}
}
}
};
var string = 'a[b][c][d][e][f][g][h][i]=j';
assert.deepEqual(qs.parse(string), expected);
通过将depth选项传递给以下可以覆盖此深度qs.parse(string, [options])
:
Copy var deep = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
assert.deepEqual(deep, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } });
当qs用于解析用户输入时,深度限制有助于减轻滥用,建议将其保持在相当小的数量。
出于类似的原因,默认情况下,qs最多只能解析1000个参数。这可以通过传递一个parameterLimit
选项来覆盖:
Copy var limited = qs.parse('a=b&c=d', { parameterLimit: 1 });
assert.deepEqual(limited, { a: 'b' });
要绕过前导问号,请使用ignoreQueryPrefix
:
Copy var prefixed = qs.parse('?a=b&c=d', { ignoreQueryPrefix: true });
assert.deepEqual(prefixed, { a: 'b', c: 'd' });
还可以传递可选的分隔符:
Copy var delimited = qs.parse('a=b;c=d', { delimiter: ';' });
assert.deepEqual(delimited, { a: 'b', c: 'd' });
分隔符也可以是正则表达式:
Copy var regexed = qs.parse('a=b;c=d,e=f', { delimiter: /[;,]/ });
assert.deepEqual(regexed, { a: 'b', c: 'd', e: 'f' });
选项allowDots
可用于启用点表示法:
Copy var withDots = qs.parse('a.b=c', { allowDots: true });
assert.deepEqual(withDots, { a: { b: 'c' } });
2.2.2 解析数组
qs也可以使用类似的[]表示法解析数组:
Copy var withArray = qs.parse('a[]=b&a[]=c');
assert.deepEqual(withArray, { a: ['b', 'c'] });
您也可以指定一个索引:
Copy var withIndexes = qs.parse('a[1]=c&a[0]=b');
assert.deepEqual(withIndexes, { a: ['b', 'c'] });
请注意,数组中的索引与对象中的键之间的唯一区别是括号之间的值必须是一个数字才能创建数组。 在创建具有特定索引的数组时,qs会将稀疏数组压缩为仅保留其顺序的现有值:
Copy var noSparse = qs.parse('a[1]=b&a[15]=c');
assert.deepEqual(noSparse, { a: ['b', 'c'] });
请注意,空字符串也是一个值,并将保留:
Copy var withEmptyString = qs.parse('a[]=&a[]=b');
assert.deepEqual(withEmptyString, { a: ['', 'b'] });
var withIndexedEmptyString = qs.parse('a[0]=b&a[1]=&a[2]=c');
assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] });
qs还会将数组中的索引指定为最大索引20
。索引大于的任何数组成员20
都将转换为以索引为键的对象:
Copy var withMaxIndex = qs.parse('a[100]=b');
assert.deepEqual(withMaxIndex, { a: { '100': 'b' } });
通过传递arrayLimit
选项可以覆盖此限制:
Copy var withArrayLimit = qs.parse('a[1]=b', { arrayLimit: 0 });
assert.deepEqual(withArrayLimit, { a: { '1': 'b' } });
要完全禁用数组解析,请设置parseArrays
为false
。
Copy var noParsingArrays = qs.parse('a[]=b', { parseArrays: false });
assert.deepEqual(noParsingArrays, { a: { '0': 'b' } });
如果混合符号,qs会将两个项合并为一个对象:
Copy var mixedNotation = qs.parse('a[0]=b&a[b]=c');
assert.deepEqual(mixedNotation, { a: { '0': 'b', b: 'c' } });
您还可以创建对象数组:
Copy var arraysOfObjects = qs.parse('a[][b]=c');
assert.deepEqual(arraysOfObjects, { a: [{ b: 'c' }] });
2.2.3 字符串化
Copy qs.stringify(object, [options]);
字符串化时,默认情况下,qs编码输出。对象按照您的预期进行字符串化:
Copy assert.equal(qs.stringify({ a: 'b' }), 'a=b');
assert.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
可以通过将encode
选项设置为以下来禁用此编码false
:
Copy var unencoded = qs.stringify({ a: { b: 'c' } }, { encode: false });
assert.equal(unencoded, 'a[b]=c');
通过将encodeValuesOnly
选项设置为true
:可以禁用键的编码:
Copy var encodedValues = qs.stringify(
{ a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
{ encodeValuesOnly: true }
);
assert.equal(encodedValues,'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h');
此编码也可以由设置为encoder
选项的自定义编码方法替换:
Copy var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str) {
// 传递的值为'a`, `b`, `c`
return // 返回已解码的字符串
}})
(注意:encoder如果encode是,该选项不适用false)
类似于encoder有一个decoder选项parse可以覆盖属性和值的解码:
Copy var decoded = qs.parse('x=z', { decoder: function (str) {
// 传递的值为'x`,`z`
return // 返回已解码的字符串
}})
超出这一点的示例将显示为输出不是为了清晰起码而编码的URI。请注意,这些情况下的返回值将在实际使用期间进行URI编码。
当数组被字符串化时,默认情况下它们被赋予显式索引:
Copy qs.stringify({ a: ['b', 'c', 'd'] });
// 'a[0]=b&a[1]=c&a[2]=d'
您可以通过将indices
选项设置为以下来覆盖此选项false
:
Copy qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
// 'a=b&a=c&a=d'
您可以使用该arrayFormat
选项指定输出数组的格式:
Copy qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
对象进行字符串化时,默认情况下使用括号表示法:
Copy qs.stringify({ a: { b: { c: 'd', e: 'f' } } });
// 'a[b][c]=d&a[b][e]=f'
您可以通过将allowDots
选项设置为以下来覆盖此选项以使用点表示法true
:
Copy qs.stringify({ a: { b: { c: 'd', e: 'f' } } }, { allowDots: true });
// 'a.b.c=d&a.b.e=f'
空字符串和空值将省略该值,但等号(=)保持不变:
Copy assert.equal(qs.stringify({ a: '' }), 'a=');
没有值的键(例如空对象或数组)将不返回任何内容:
Copy assert.equal(qs.stringify({ a: [] }), '');
assert.equal(qs.stringify({ a: {} }), '');
assert.equal(qs.stringify({ a: [{}] }), '');
assert.equal(qs.stringify({ a: { b: []} }), '');
assert.equal(qs.stringify({ a: { b: {}} }), '');
设置为的属性undefined
将完全省略:
Copy assert.equal(qs.stringify({ a: null, b: undefined }), 'a=');
可以选择在查询字符串前面添加问号:
Copy assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');
分隔符也可以用stringify
覆盖:
Copy assert.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
如果您只想覆盖Date
对象的序列化,则可以提供以下serializeDate
选项:
Copy var date = new Date(7);
assert.equal(qs.stringify({ a: date }), 'a=1970-01-01T00:00:00.007Z'.replace(/:/g, '%3A'));
assert.equal(
qs.stringify({ a: date }, { serializeDate: function (d) { return d.getTime(); } }),
'a=7'
);
您可以使用该sort
选项来影响参数键的顺序:
Copy function alphabeticalSort(a, b) {
return a.localeCompare(b);
}
assert.equal(qs.stringify({ a: 'c', z: 'y', b : 'f' }, { sort: alphabeticalSort }), 'a=c&b=f&z=y');
最后,您可以使用该filter
选项来限制字符串化输出中包含哪些键。 如果传递函数,将为每个键调用它以获取替换值。否则,如果传递数组,它将用于选择字符串化的属性和数组索引:
Copy function filterFunc(prefix, value) {
if (prefix == 'b') {
// 返回`undefined`值以省略属性。
return;
}
if (prefix == 'e[f]') {
return value.getTime();
}
if (prefix == 'e[g][0]') {
return value * 2;
}
return value;
}
qs.stringify({ a: 'b', c: 'd', e: { f: new Date(123), g: [2] } }, { filter: filterFunc });
// 'a=b&c=d&e[f]=123&e[g][0]=4'
qs.stringify({ a: 'b', c: 'd', e: 'f' }, { filter: ['a', 'e'] });
// 'a=b&e=f'
qs.stringify({ a: ['b', 'c', 'd'], e: 'f' }, { filter: ['a', 0, 2] });
// 'a[0]=b&a[2]=d'
2.3 示例
2.3.1 qs.parse()
将URL解析成对象的形式
Copy const Qs = require('qs');
let url = 'method=query_sql_dataset_data&projectId=85&appToken=7d22e38e-5717-11e7-907b-a6006ad3dba0';
Qs.parse(url);
console.log(Qs.parse(url));
如上面代码所示,输出结果如下:
Copy {
method: 'query_sql_dataset_data',
projectId: '85',
appToken: '7d22e38e-5717-11e7-907b-a6006ad3dba0'
}
2.3.2 qs.stringify()
将对象 序列化成URL的形式,以&进行拼接
Copy const Qs = require('qs');
let obj= {
method: "query_sql_dataset_data",
projectId: "85",
appToken: "7d22e38e-5717-11e7-907b-a6006ad3dba0",
datasetId: " 12564701"
};
Qs.stringify(obj);
console.log(Qs.stringify(obj));
如上面代码所示,输出结果如下:
Copy method=query_sql_dataset_data&projectId=85&appToken=7d22e38e-5717-11e7-907b-a6006ad3dba0&datasetId=12564701
那么当我们需要传递数组的时候,我们就可以通过下面方式进行处理:
默认情况下,它们给出明确的索引,如下代码:
Copy qs.stringify({ a: ['b', 'c', 'd'] });
// 'a[0]=b&a[1]=c&a[2]=d'
也可以进行重写这种默认方式为false
Copy qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
// 'a=b&a=c&a=d'
当然,也可以通过arrayFormat
选项进行格式化输出,如下代码所示:
Copy qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
在这里需要注意的是,JSON中同样存在stringify方法,但是两者之间的区别是很明显的,如下所示:
Copy const Qs = require('qs');
let obj= {
uid: "cs11",
pwd: "000000als",
username: "cs11",
password: " 000000als"
};
Qs.stringify(obj);
console.log(JSON.stringify(param));
// '{"uid":"cs11","pwd":"000000als","username":"cs11","password":"000000als"}'
console.log(Qs.stringify(obj));
// 'uid=cs11&pwd=000000als&username=cs11&password=000000als'
如上所示,前者是采用JSON.stringify(param)进行处理,后者是采用Qs.stringify(param)进行处理的。
3 最佳实践
3.1 qs.stringify进阶使用
qs.stringify
则和 qs.parse
相反,是把一个参数对象格式化为一个字符串。
Copy let params = { c: 'b', a: 'd' };
qs.stringify(params)
// 结果是
'c=b&a=d'
甚至可以对格式化后的参数进行排序:
Copy let params = { c: 'd', a: 'b' };
qs.stringify(params, (a,b) => a.localeCompare(b))
// 结果是
'a=b&c=d'
Copy let params = [1, 2, 3];
// indices(默认)
qs.stringify({a: params}, {
arrayFormat: 'indices'
})
// 结果是
'a[0]=1&a[1]=2&a[2]=3'
// brackets
qs.stringify({a: params}, {
arrayFormat: 'brackets'
})
// 结果是
'a[]=1&a[]=2&a[]=3'
// repeat
qs.stringify({a: params}, {
arrayFormat: 'repeat'
})
// 结果是
'a=1&a=2&a=3'
在默认情况下,json格式的参数会用 []
方式编码,
Copy let json = { a: { b: { c: 'd', e: 'f' } } };
qs.stringify(json);
//结果 'a[b][c]=d&a[b][e]=f'
但是某些服务端框架,并不能很好的处理这种格式,所以需要转为下面的格式
Copy qs.stringify(json, {allowDots: true});
//结果 'a.b.c=d&a.b.e=f'
参考资料