简介

Nodejs中内置了一个querystring模块,可以用来解析与格式化 URL 查询字符串,但是它的功能比较简单,无法解析嵌套的对象或者数组等,还有无法解析null或者一些特殊字符,因此,qs.js就是为了解决这些问题而诞生的。

解析对象

var qs = require('qs');
qs.parse(string, [options]);

qs允许你使用[]来创建一个嵌套的对象:

qs.parse('foo[bar]=baz')

将会解析为:

{
    foo: {
        bar: 'baz'
    }
}

如果使用querystring模块进行解析的话,则会出现如下的效果:

{ 'foo[bar]': 'baz' }

如果你加入了{ plainObjects: true }选项,则会通过Object.create(null)来创建一个对象,该对象上不会有任何原型,如果使用{ allowPrototypes: true }选项,则该对象上有原型:

var nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true });
var protoObject = qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true });
nullObject.toString //undefined
protoObject.toString // [Function: toString]

URI编码格式将会被解析成:

qs.parse('a%5Bb%5D=c')// a: { b: 'c' }

qs也支持嵌套解析,但是默认只解析到第5层:

var string = 'a[b][c][d][e][f][g][h][i]=j';
qs.parse(string)
//输出如下
{
    a: {
        b: {
            c: {
                d: {
                    e: {
                        f: {
                            '[g][h][i]': 'j'
                        }
                    }
                }
            }
        }
    }
}

你可以自行设置解析的嵌套深度{ depth: 5 },默认地,qs会解析最多1000个参数,你可以通过设置parameterLimit来自行设置:

var limited = qs.parse('a=b&c=d', { parameterLimit: 1 }); //{ a: 'b' }

如果想要忽略?,可以设置ignoreQueryPrefix:

var prefixed = qs.parse('?a=b&c=d', { ignoreQueryPrefix: true });//{ a: 'b', c: 'd' }

你也可以通过设置delimiter来自定义分隔符:

var delimited = qs.parse('a=b;c=d', { delimiter: ';' }); //{ a: 'b', c: 'd' }
var regexed = qs.parse('a=b;c=d,e=f', { delimiter: /[;,]/ }); //{ a: 'b', c: 'd', e: 'f' }

你可以设置allowDots来解析使用.的标记语法:

var withDots = qs.parse('a.b=c', { allowDots: true }); //{ a: { b: 'c' } }

解析数组

你可以使用[]告诉qs这是一个数组:

var withArray = qs.parse('a[]=b&a[]=c'); //{ a: ['b', 'c'] }
var withIndexes = qs.parse('a[1]=c&a[0]=b'); //{ a: ['b', 'c'] }
var withEmptyString = qs.parse('a[]=&a[]=b'); //{ a: ['', 'b'] }
var withIndexedEmptyString = qs.parse('a[0]=b&a[1]=&a[2]=c'); //{ a: ['b', '', 'c'] }

注意[index]虽然可以表示数组的索引,但是最多不能超过20,如果超过了就会把它当做一个属性来解析,你也可以通过设置arrayLimit来指定最大索引上限,parseArrays是否解析成数组:

var withMaxIndex = qs.parse('a[100]=b'); //{ a: { '100': 'b' } }
var withArrayLimit = qs.parse('a[1]=b', { arrayLimit: 0 }); //{ a: { '1': 'b' } }
var noParsingArrays = qs.parse('a[]=b', { parseArrays: false });//{ a: { '0': 'b' } }

字符化

默认的qs会使用URI编码格式输出,你也可以使用{ encode: false }来关闭:

qs.stringify({ a: { b: 'c' } })//'a%5Bb%5D=c'
 qs.stringify({ a: { b: 'c' } }, { encode: false }); //'a[b]=c'

你可以设置{ encodeValuesOnly: true }选项来控制只对值进行编码:

qs.stringify(
    { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
    { encodeValuesOnly: true }
);//'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'

你还可以通过设置encoder选项来自定义编码方法:

var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str) {
    // a[b], c
    return str+'-'
}})// a[b]-=c-

注意,如果encodefalse时,encoder则不起作用。

对应地,你也可以设置decoder选项来自定义解码方法。

var decoded = qs.parse('x=z', { decoder: function (str) {
    // `x`, `z`
    return // Return decoded string
}})

更多用法请参考以下例子:

qs.stringify({ a: ['b', 'c', 'd'] }); // 'a[0]=b&a[1]=c&a[2]=d'
qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false }); // 'a=b&a=c&a=d'
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'
qs.stringify({ a: { b: { c: 'd', e: 'f' } } });// 'a[b][c]=d&a[b][e]=f'
qs.stringify({ a: { b: { c: 'd', e: 'f' } } }, { allowDots: true });// 'a.b.c=d&a.b.e=f'
qs.stringify({ a: '' })// 'a='
qs.stringify({ a: [] }) //''
qs.stringify({ a: {} })//''
qs.stringify({ a: [{}] })//''
qs.stringify({ a: { b: []} })//''
qs.stringify({ a: { b: {}} })//''
qs.stringify({ a: null, b: undefined })//'a='
qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true })//'?a=b&c=d'
qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' })//'a=b;c=d'
qs.stringify({ a: date }, { serializeDate: function (d) { return d.getTime(); } })//'a=7'

你还可以自定义sort属性方法来对属性进行排序:

function alphabeticalSort(a, b) {
    return a.localeCompare(b);
}
qs.stringify({ a: 'c', z: 'y', b : 'f' }, { sort: alphabeticalSort })//'a=c&b=f&z=y'

你还可以通过设置filter属性方法来设置过滤器对属性进行过滤:

function filterFunc(prefix, value) {
    if (prefix == 'b') {
        // Return an `undefined` value to omit a property.
        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'

Null值处理

默认地,Null值会被解析为空字符串:

var withNull = qs.stringify({ a: null, b: '' }); //'a=&b='

解析器不区分参数是否含有等号或者不含都会转换为空:

qs.parse('a&b='); //{ a: '', b: '' }

在字符串化的时候,可以使用strictNullHandling选项来区分Null值和空,输出结果中Null值将不会含有等号:

qs.stringify({ a: null, b: '' }, { strictNullHandling: true })//'a&b='

如果要将没有等号的字符串值解析为Null的话,也可以使用strictNullHandling选项:

qs.parse('a&b=', { strictNullHandling: true }) //{ a: null, b: '' }

skipNulls选项可以忽略Null值的解析:

qs.stringify({ a: 'b', c: null}, { skipNulls: true }) //'a=b'

处理特殊字符集

qs.js默认使用的是utf-8进行编码和解码,如果你想要处理特殊字符集的话,你可以使用qs-iconv

var encoder = require('qs-iconv/encoder')('shift_jis');
var shiftJISEncoded = qs.stringify({ a: 'こんにちは!' }, { encoder: encoder });//'a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I'
var decoder = require('qs-iconv/decoder')('shift_jis');
var obj = qs.parse('a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I', { decoder: decoder })//{ a: 'こんにちは!' }

RFC 3986 和RFC 1738 空格编码

RFC3986默认会将空格解析为%20,RFC1738会把空格解析为+:

qs.stringify({ a: 'b c' })//'a=b%20c'
qs.stringify({ a: 'b c' }, { format : 'RFC3986' })// 'a=b%20c'
qs.stringify({ a: 'b c' }, { format : 'RFC1738' })//'a=b+c'

参考