qs文档-中文翻译

npm地址

一个添加了一些安全性的查询字符串解析和字符串化库。

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');

解析对象

qs.parse(string, [options]);

qs 允许你在查询字符串中通过使用方括号 [] 来创建嵌套对象。例如,字符串 'foo[bar]=baz' 会被转换为:

assert.deepEqual(qs.parse('foo[bar]=baz'), {
    foo: {
        bar: 'baz'
    }
});

使用 plainObjects 选项时,解析后的值会以 null 对象的形式返回,它通过 Object.create(null) 创建。因此需要注意,该对象不会继承原型方法,用户可以将这些方法名设置为任意值:

var nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true });
assert.deepEqual(nullObject, { a: { hasOwnProperty: 'b' } });

默认情况下,参数将不会覆盖对象原型上的属性。如果希望保留这些字段的数据,可以使用前面提到的 plainObjects,或者将 allowPrototypes 设置为 true,这将允许用户输入覆盖这些属性。警告:启用此选项通常不是一个好主意,因为它可能在使用被覆盖的属性时引发问题。因此在使用此选项时应小心。

var protoObject = qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true });
assert.deepEqual(protoObject, { a: { hasOwnProperty: 'b' } });

URI 编码的字符串同样有效:

assert.deepEqual(qs.parse('a%5Bb%5D=c'), {
    a: { b: 'c' }
});

你还可以嵌套对象,例如 'foo[bar][baz]=foobarbaz'

assert.deepEqual(qs.parse('foo[bar][baz]=foobarbaz'), {
    foo: {
        bar: {
            baz: 'foobarbaz'
        }
    }
});

默认情况下,qs 解析嵌套对象的深度限制为 5 层。这意味着如果尝试解析像 'a[b][c][d][e][f][g][h][i]=j' 这样的字符串,结果对象将是:

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 选项来重写深度限制:

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' } } });

使用 strictDepth 选项(默认为 false),qs 可以在解析嵌套输入超出深度限制时抛出错误:

try {
    qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1, strictDepth: true });
} catch (err) {
    assert(err instanceof RangeError);
    assert.strictEqual(err.message, 'Input depth exceeded depth option of 1 and strictDepth is true');
}

深度限制有助于防止滥用 qs 解析用户输入,因此建议保持较小的数值。strictDepth 选项通过在超过限制时抛出错误,为此提供了另一层保护,让你可以捕捉和处理这种情况。

出于类似的原因,qs 默认只会解析最多 1000 个参数。可以通过 parameterLimit 选项来重写这个限制:

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

为了跳过前导的问号,可以使用 ignoreQueryPrefix

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

也可以传入自定义的分隔符:

var delimited = qs.parse('a=b;c=d', { delimiter: ';' });
assert.deepEqual(delimited, { a: 'b', c: 'd' });

分隔符也可以是正则表达式:

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

allowDots 选项可以启用点号表示法:

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

decodeDotInKeys 选项用于解码键中的点号。注意:它隐含了 allowDots,因此如果将 decodeDotInKeys 设置为 trueallowDotsfalse,则解析将出错。

var withDots = qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { decodeDotInKeys: true });
assert.deepEqual(withDots, { 'name.obj': { first: 'John', last: 'Doe' }});

allowEmptyArrays 选项允许对象中存在空数组值:

var withEmptyArrays = qs.parse('foo[]&bar=baz', { allowEmptyArrays: true });
assert.deepEqual(withEmptyArrays, { foo: [], bar: 'baz' });

duplicates 选项可以改变处理重复键的行为:

assert.deepEqual(qs.parse('foo=bar&foo=baz'), { foo: ['bar', 'baz'] });
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'combine' }), { foo: ['bar', 'baz'] });
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'first' }), { foo: 'bar' });
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'last' }), { foo: 'baz' });

如果需要处理遗留浏览器或服务,qs 还支持将百分比编码的八位字节解码为 iso-8859-1 编码:

var oldCharset = qs.parse('a=%A7', { charset: 'iso-8859-1' });
assert.deepEqual(oldCharset, { a: '§' });

某些服务会在表单中添加初始的 utf8=✓ 值,以便旧版 Internet Explorer 更有可能以 utf-8 提交表单。此外,服务器可以检查该值与错误编码的对勾字符,从而检测查询字符串或 application/x-www-form-urlencoded 消息体未按 utf-8 发送,例如如果表单有 accept-charset 参数或包含页面有不同字符集。

qs 通过 charsetSentinel 选项支持这种机制。如果指定了此选项,解析后的对象将忽略 utf8 参数。它会根据对勾字符的编码方式切换到 iso-8859-1 或 utf-8 模式。

重要提示:当同时指定 charset 选项和 charsetSentinel 选项时,charset 将在请求包含 utf8 参数时被覆盖,从而根据实际字符集推断。也就是说,charset 将表现为默认字符集,而不是权威字符集。

var detectedAsUtf8 = qs.parse('utf8=%E2%9C%93&a=%C3%B8', {
    charset: 'iso-8859-1',
    charsetSentinel: true
});
assert.deepEqual(detectedAsUtf8, { a: 'ø' });

// 浏览器在以 iso-8859-1 提交时将对勾编码为 ✓:
var detectedAsIso8859_1 = qs.parse('utf8=%26%2310003%3B&a=%F8', {
    charset: 'utf-8',
    charsetSentinel: true
});
assert.deepEqual(detectedAsIso8859_1, { a: 'ø' });

如果希望将 &#...; 语法解码为实际字符,可以指定 interpretNumericEntities 选项:

var detectedAsIso8859_1 = qs.parse('a=%26%239786%3B', {
    charset: 'iso-8859-1',
    interpretNumericEntities: true
});
assert.deepEqual(detectedAsIso8859_1, { a: '☺' });

当字符集在 charsetSentinel 模式中被检测到时,这个选项也有效。

数组解析

qs 也可以使用类似 [] 的表示法来解析数组:

var withArray = qs.parse('a[]=b&a[]=c');
assert.deepEqual(withArray, { a: ['b', 'c'] });

你也可以指定索引:

var withIndexes = qs.parse('a[1]=c&a[0]=b');
assert.deepEqual(withIndexes, { a: ['b', 'c'] });

注意,数组中的索引与对象中的键之间唯一的区别是括号中的值必须是数字,以创建数组。当使用特定索引创建数组时,qs 会压缩稀疏数组,只保留现有值并保持其顺序:

var noSparse = qs.parse('a[1]=b&a[15]=c');
assert.deepEqual(noSparse, { a: ['b', 'c'] });

你还可以使用 allowSparse 选项来解析稀疏数组:

var sparseArray = qs.parse('a[1]=2&a[3]=5', { allowSparse: true });
assert.deepEqual(sparseArray, { a: [, '2', , '5'] });

注意,空字符串也是一个值,并将被保留:

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 的数组成员将被转换为一个对象,以索引作为键。这是为了处理某些情况下,如 a[999999999] 的情况,这将需要大量时间来迭代这个巨大的数组。

var withMaxIndex = qs.parse('a[100]=b');
assert.deepEqual(withMaxIndex, { a: { '100': 'b' } });

这个限制可以通过传递 arrayLimit 选项来覆盖:

var withArrayLimit = qs.parse('a[1]=b', { arrayLimit: 0 });
assert.deepEqual(withArrayLimit, { a: { '1': 'b' } });

要完全禁用数组解析,可以将 parseArrays 设置为 false

var noParsingArrays = qs.parse('a[]=b', { parseArrays: false });
assert.deepEqual(noParsingArrays, { a: { '0': 'b' } });

如果你混合使用不同的表示法,qs 将把两个项合并为一个对象:

var mixedNotation = qs.parse('a[0]=b&a[b]=c');
assert.deepEqual(mixedNotation, { a: { '0': 'b', b: 'c' } });

你还可以创建对象的数组:

var arraysOfObjects = qs.parse('a[][b]=c');
assert.deepEqual(arraysOfObjects, { a: [{ b: 'c' }] });

一些人使用逗号连接数组,qs 可以解析它:

var arraysOfObjects = qs.parse('a=b,c', { comma: true })
assert.deepEqual(arraysOfObjects, { a: ['b', 'c'] });

(这不能转换嵌套对象,例如 a={b:1},{c:d}

解析原始/标量值(数字、布尔值、null 等)

默认情况下,所有值都被解析为字符串。这种行为不会改变,并在问题 #91 中解释。

var primitiveValues = qs.parse('a=15&b=true&c=null');
assert.deepEqual(primitiveValues, { a: '15', b: 'true', c: 'null' });

如果你希望自动转换看起来像数字、布尔值和其他值的原始对应物,你可以使用 query-types Express JS 中间件,它会自动转换所有请求查询参数。

字符串化

qs.stringify(object, [options])

在字符串化时,qs 默认会对输出进行 URI 编码。对象会按照预期的方式被字符串化:

assert.equal(qs.stringify({ a: 'b' }), 'a=b');
assert.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');

可以通过将 encode 选项设置为 false 来禁用此编码:

复制代码
var unencoded = qs.stringify({ a: { b: 'c' } }, { encode: false });
assert.equal(unencoded, 'a[b]=c');

禁用键的编码

可以通过将 encodeValuesOnly 选项设置为 true 来禁用键的编码:

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 选项的自定义编码方法进行替换:

var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str) {
    // 传入的值 `a`、`b`、`c`
    return // 返回编码后的字符串
}});

注意: 如果 encodefalse,则 encoder 选项不适用。

encoder 类似,parse 也有一个 decoder 选项,用于覆盖属性和值的解码:

var decoded = qs.parse('x=z', { decoder: function (str) {
    // 传入的值 `x`、`z`
    return // 返回解码后的字符串
}});

您可以使用传递给编码器的 type 参数对键和值进行不同逻辑的编码:

var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultEncoder, charset, type) {
    if (type === 'key') {
        return // 编码后的键
    } else if (type === 'value') {
        return // 编码后的值
    }
}});

type 参数也适用于解码器:

var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) {
    if (type === 'key') {
        return // 解码后的键
    } else if (type === 'value') {
        return // 解码后的值
    }
}});

从此处开始的示例将显示输出未进行 URI 编码以便于理解。请注意,在实际使用中,这些情况下的返回值将被 URI 编码。

当数组被字符串化时,它们遵循 arrayFormat 选项,默认为索引:

qs.stringify({ a: ['b', 'c', 'd'] });
// 'a[0]=b&a[1]=c&a[2]=d'

您可以通过将 indices 选项设置为 false 来覆盖此设置,或者更明确地将 arrayFormat 选项设置为 repeat

qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
// 'a=b&a=c&a=d'

你可以使用 arrayFormat 选项来指定输出数组的格式:

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'] }, { arrayFormat: 'comma' })
// 'a=b,c'

注意: 当使用 arrayFormat 设置为 'comma' 时,你可以传递 commaRoundTrip 选项并设置为 truefalse,以便在只有一个元素的数组时追加 [],使其能够通过 parse 函数回传。

对象的字符串化

当对象被字符串化时,默认使用括号表示法:

qs.stringify({ a: { b: { c: 'd', e: 'f' } } });
// 'a[b][c]=d&a[b][e]=f'

你可以通过设置 allowDots 选项为 true 来使用点号表示法:

qs.stringify({ a: { b: { c: 'd', e: 'f' } } }, { allowDots: true });
// 'a.b.c=d&a.b.e=f'

你可以使用 encodeDotInKeys 选项将点号编码到对象的键中:

qs.stringify({ "name.obj": { "first": "John", "last": "Doe" } }, { allowDots: true, encodeDotInKeys: true });
// 'name%252Eobj.first=John&name%252Eobj.last=Doe'

允许空数组值

你可以通过设置 allowEmptyArrays 选项为 true 来允许空数组值:

qs.stringify({ foo: [], bar: 'baz' }, { allowEmptyArrays: true });
// 'foo[]&bar=baz'

空字符串和 null

空字符串和 null 值会省略值,但等号 = 仍然存在:

assert.equal(qs.stringify({ a: '' }), 'a=');

没有值的键(如空对象或数组)将返回空:

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 的属性

设置为 undefined 的属性将被完全忽略:

assert.equal(qs.stringify({ a: null, b: undefined }), 'a=');

添加问号前缀

查询字符串可以选择性地在前面加上问号:

assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');

自定义分隔符

你可以使用 stringify 来覆盖默认分隔符:

assert.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');

日期序列化

如果你只想覆盖 Date 对象的序列化,可以提供 serializeDate 选项:

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 选项来影响参数键的顺序:

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 选项

你可以使用 filter 选项来限制哪些键会包含在字符串化输出中。如果你传递一个函数,它会被每个键调用以获取替代值。否则,如果你传递一个数组,它将用于选择要字符串化的属性和数组索引:

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'

处理 null

默认情况下,null 值会被处理为空字符串:

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

要完全跳过 null 值的渲染,请使用 skipNulls 选项:

var nullsSkipped = qs.stringify({ a: 'b', c: null}, { skipNulls: true });
assert.equal(nullsSkipped, 'a=b');

自定义字符集

如果你与遗留系统通信,可以使用 charset 选项切换到 iso-8859-1

var iso = qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' });
assert.equal(iso, '%E6=%E6');

字符集中的字符将被转换为数字实体:

var numeric = qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' });
assert.equal(numeric, 'a=%26%239786%3B');
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容