ljharb/qs

GitHub: ljharb/qs

一个支持嵌套对象和数组的 JavaScript 查询字符串解析与序列化库。

Stars: 8942 | Forks: 890

qs

# qs [![Version Badge](https://versionbadg.es/ljharb/qs.svg)][package-url] [![github actions](https://img.shields.io/github/check-runs/ljharb/qs/main)][actions-url] [![coverage](https://codecov.io/gh/ljharb/qs/branch/main/graphs/badge.svg)][codecov-url] [![License](https://img.shields.io/npm/l/qs.svg)][license-url] [![Downloads](https://img.shields.io/npm/dm/qs.svg)][downloads-url] [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/9058/badge)](https://bestpractices.coreinfrastructure.org/projects/9058) [![npm badge](https://nodei.co/npm/qs.png?downloads=true&stars=true)][package-url] 一个增加了部分安全性的 querystring 解析和字符串化库。 主要维护者:[Jordan Harband](https://github.com/ljharb) **qs** 模块最初由 [TJ Holowaychuk](https://github.com/visionmedia/node-querystring) 创建和维护。 ## 用法 ``` 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'); ``` ### 解析对象 [](#preventEval) ``` qs.parse(string, [options]); ``` **qs** 允许你通过在子键名称周围加上方括号 `[]`,在 query string 中创建嵌套对象。 例如,字符串 `'foo[bar]=baz'` 会转换为: ``` assert.deepEqual(qs.parse('foo[bar]=baz'), { foo: { bar: 'baz' } }); ``` 当使用 `plainObjects` 选项时,解析后的值会作为 null 对象返回(通过 `{ __proto__: null }` 创建),因此你需要注意它上面不会存在 prototype 方法,并且用户可以将这些名称设置为他们喜欢的任何值: ``` var nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true }); assert.deepEqual(nullObject, { a: { hasOwnProperty: 'b' } }); ``` 默认情况下,会忽略那些会覆盖对象 prototype 属性的参数。如果你想保留这些字段的数据,可以使用上面提到的 `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); ``` 可以通过向 `qs.parse(string, [options])` 传递 `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' }); ``` 如果你希望在超过限制(例如 `parameterLimit`、`arrayLimit`)时抛出错误,请将 `throwOnLimitExceeded` 选项设置为 `true`。如果 query string 超过了配置的限制,此选项将生成一个描述性错误。 ``` try { qs.parse('a=1&b=2&c=3&d=4', { parameterLimit: 3, throwOnLimitExceeded: true }); } catch (err) { assert(err instanceof Error); assert.strictEqual(err.message, 'Parameter limit exceeded. Only 3 parameters allowed.'); } ``` 当 `throwOnLimitExceeded` 设置为 `false`(默认)时,**qs** 会解析到指定的 `parameterLimit` 为止,并在不抛出错误的情况下忽略其余部分。 要忽略前导问号,请使用 `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` 设置为 `true`,而将 `allowDots` 设置为 `false`,`parse` 将会报错。 ``` 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' }); ``` 请注意,带有方括号表示法 (`[]`) 的键总是会合并到数组中,而不管 `duplicates` 的设置如何: ``` assert.deepEqual(qs.parse('a=1&a=2&b[]=1&b[]=2', { duplicates: 'last' }), { a: '2', b: ['1', '2'] }); ``` 如果你必须处理旧版浏览器或服务,还支持将百分号编码的八位字节解码为 iso-8859-1: ``` var oldCharset = qs.parse('a=%A7', { charset: 'iso-8859-1' }); assert.deepEqual(oldCharset, { a: '§' }); ``` 一些服务会在表单中添加一个初始的 `utf8=✓` 值,以便旧版本的 Internet Explorer 更有可能以 utf-8 编码提交表单。 此外,服务器可以根据勾号字符的错误编码检查该值,并检测 query string 或 `application/x-www-form-urlencoded` body 是否*不是*以 utf-8 发送的(例如,如果表单具有 `accept-charset` 参数,或者包含它的页面具有不同的字符集)。 **qs** 通过 `charsetSentinel` 选项支持此机制。 如果指定了该选项,返回的对象中将省略 `utf8` 参数。 它将根据勾号的编码方式用于切换到 `iso-8859-1`/`utf-8` 模式。 **重要**:当你同时指定 `charset` 选项和 `charsetSentinel` 选项时,如果请求包含可以从中推断出实际字符集的 `utf8` 参数,`charset` 将被覆盖。 从这个意义上说,`charset` 将作为默认字符集而不是权威字符集。 ``` var detectedAsUtf8 = qs.parse('utf8=%E2%9C%93&a=%C3%B8', { charset: 'iso-8859-1', charsetSentinel: true }); assert.deepEqual(detectedAsUtf8, { a: 'ø' }); // Browsers encode the checkmark as ✓ when submitting as 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' } }); ``` 如果你想在超过数组限制时抛出错误,请将 `throwOnLimitExceeded` 选项设置为 `true`。如果 query string 超过了配置的限制,此选项将生成一个描述性错误。 ``` try { qs.parse('a[1]=b', { arrayLimit: 0, throwOnLimitExceeded: true }); } catch (err) { assert(err instanceof Error); assert.strictEqual(err.message, 'Array limit exceeded. Only 0 elements allowed in an array.'); } ``` 当 `throwOnLimitExceeded` 设置为 `false`(默认)时,**qs** 会解析到指定的 `arrayLimit` 为止,如果超过限制,该数组将转换为以索引为键的对象 要防止数组语法(`a[]`, `a[0]`)被解析为数组,请将 `parseArrays` 设置为 `false`。 请注意,当 `duplicates` 为 `'combine'`(默认值)时,重复键(例如 `a=b&a=c`)仍可能产生数组。 ``` 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' } }); ``` 当一个键同时作为普通值和对象出现时,默认情况下 **qs** 会将冲突的值包装在一个数组中(`strictMerge` 默认为 `true`): ``` assert.deepEqual(qs.parse('a[b]=c&a=d'), { a: [{ b: 'c' }, 'd'] }); assert.deepEqual(qs.parse('a=d&a[b]=c'), { a: ['d', { b: 'c' }] }); ``` 要恢复旧行为(即使用原始值作为键且值为 `true`),请将 `strictMerge` 设置为 `false`: ``` assert.deepEqual(qs.parse('a[b]=c&a=d', { strictMerge: false }), { a: { b: 'c', d: true } }); ``` 你也可以创建对象数组: ``` 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 等) 默认情况下,所有值都被解析为字符串。 此行为不会改变,并在 [issue #91](https://github.com/ljharb/qs/issues/91) 中进行了解释。 ``` var primitiveValues = qs.parse('a=15&b=true&c=null'); assert.deepEqual(primitiveValues, { a: '15', b: 'true', c: 'null' }); ``` 如果你希望将看起来像数字、布尔值和其他值的值自动转换为其对应的原始类型,你可以使用 [query-types Express JS 中间件](https://github.com/xpepermint/query-types),它会自动转换所有请求的 query 参数。 ### 字符串化 [](#preventEval) ``` 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) { // Passed in values `a`, `b`, `c` return // Return encoded string }}) ``` _(注意:如果 `encode` 为 `false`,则 `encoder` 选项不适用)_ 与 `encoder` 类似,`parse` 也有一个 `decoder` 选项,用于覆盖属性和值的解码: ``` var decoded = qs.parse('x=z', { decoder: function (str) { // Passed in values `x`, `z` return // Return decoded string }}) ``` 你可以通过使用提供给 encoder 的 type 参数,使用不同的逻辑对键和值进行编码: ``` var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultEncoder, charset, type) { if (type === 'key') { return // Encoded key } else if (type === 'value') { return // Encoded value } }}) ``` type 参数也会提供给 decoder: ``` var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) { if (type === 'key') { return // Decoded key } else if (type === 'value') { return // Decoded value } }}) ``` 为了清晰起见,之后的示例将假定输出未进行 URI 编码。 请注意,在实际使用中,这些情况下的返回值*将会*进行 URI 编码。 对数组进行字符串化时,它们遵循 `arrayFormat` 选项,该选项默认为 `indices`: ``` 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` 选项设置为 `true` 或 `false`,以便在单元素数组上附加 `[]`,从而使它们可以通过 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` 选项设置为 `true` 来对对象键中的点表示法进行编码: 注意:它隐含了 `allowDots`,因此如果你将 `decodeDotInKeys` 设置为 `true`,而将 `allowDots` 设置为 `false`,`stringify` 将会报错。 注意事项:当 `encodeValuesOnly` 为 `true` 且 `encodeDotInKeys` 也为 `true` 时,只会对键中的点进行编码,其他内容则不会。 ``` 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 值将省略该值,但等号 (=) 会保留在原处: ``` 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` 的属性将被完全省略: ``` assert.equal(qs.stringify({ a: null, b: undefined }), 'a='); ``` query string 可以选择性地加上问号前缀: ``` assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d'); ``` 请注意,当输出为空字符串时,不会添加前缀: ``` assert.equal(qs.stringify({}, { addQueryPrefix: true }), ''); ``` 在 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` 选项来限制字符串化输出中包含哪些键。 如果你传递一个函数,它将为每个键调用以获取替换值。 否则,如果你传递一个数组,它将用于选择要进行字符串化的属性和数组索引: ``` 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' ``` 你也可以使用 `filter` 为用户自定义类型注入自定义序列化。 假设你正在使用某个期望范围格式 query string 的 API: ``` https://domain.com/endpoint?range=30...70 ``` 你将其建模为: ``` class Range { constructor(from, to) { this.from = from; this.to = to; } } ``` 你可以_注入_一个自定义序列化器来处理这种类型的值: ``` qs.stringify( { range: new Range(30, 70), }, { filter: (prefix, value) => { if (value instanceof Range) { return `${value.from}...${value.to}`; } // serialize the usual way return value; }, } ); // range=30...70 ``` ### 处理 `null` 值 默认情况下,`null` 值被视为空字符串: ``` var withNull = qs.stringify({ a: null, b: '' }); assert.equal(withNull, 'a=&b='); ``` 解析不会区分带等号和不带等号的参数。 两者都会转换为空字符串。 ``` var equalsInsensitive = qs.parse('a&b='); assert.deepEqual(equalsInsensitive, { a: '', b: '' }); ``` 要区分 `null` 值和空字符串,请使用 `strictNullHandling` 标志。在生成的字符串中,`null` 值没有 `=` 号: ``` var strictNull = qs.stringify({ a: null, b: '' }, { strictNullHandling: true }); assert.equal(strictNull, 'a&b='); ``` 要将没有 `=` 的值解析回 `null`,请使用 `strictNullHandling` 标志: ``` var parsedStrictNull = qs.parse('a&b=', { strictNullHandling: true }); assert.deepEqual(parsedStrictNull, { a: null, b: '' }); ``` 要完全跳过渲染具有 `null` 值的键,请使用 `skips` 标志: ``` 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'); ``` `iso-8859-1` 中不存在的字符将被转换为数字实体,类似于浏览器的做法: ``` var numeric = qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }); assert.equal(numeric, 'a=%26%239786%3B'); ``` 你可以使用 `charsetSentinel` 选项,通过包含一个带有正确编码勾号的 `utf8=✓` 参数来声明字符,类似于 Ruby on Rails 等在提交表单时的做法。 ``` var sentinel = qs.stringify({ a: '☺' }, { charsetSentinel: true }); assert.equal(sentinel, 'utf8=%E2%9C%93&a=%E2%98%BA'); var isoSentinel = qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }); assert.equal(isoSentinel, 'utf8=%26%2310003%3B&a=%E6'); ``` ### 处理特殊字符集 默认情况下,字符的编码和解码是在 `utf-8` 中完成的,并且通过 `charset` 参数也内置了对 `iso-8859-1` 的支持。 如果你希望将 querystring 编码为不同的字符集(例如 [Shift JIS](https://en.wikipedia.org/wiki/Shift_JIS)),你可以使用 [`qs-iconv`](https://github.com/martinheidegger/qs-iconv) 库: ``` var encoder = require('qs-iconv/encoder')('shift_jis'); var shiftJISEncoded = qs.stringify({ a: 'こんにちは!' }, { encoder: encoder }); assert.equal(shiftJISEncoded, 'a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I'); ``` 这也适用于 query string 的解码: ``` 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 }); assert.deepEqual(obj, { a: 'こんにちは!' }); ``` ### RFC 3986 和 RFC 1738 空格编码 RFC3986 用作默认选项,并将 ' ' 编码为 *%20*,这是向后兼容的。 同时,根据 RFC1738,输出可以被字符串化,其中 ' ' 等于 '+'。 ``` assert.equal(qs.stringify({ a: 'b c' }), 'a=b%20c'); assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC3986' }), 'a=b%20c'); assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c'); ``` ## 安全 如果你有潜在的安全漏洞需要报告,请发送电子邮件给 [@ljharb](https://github.com/ljharb) 或访问 https://tidelift.com/security。 ## 面向企业的 qs 作为 Tidelift 订阅的一部分提供 qs 以及数千个其他包的维护者正在与 Tidelift 合作,为你用于构建应用程序的开源依赖项提供商业支持和维护。 节省时间,降低风险,改善代码健康状况,同时为你所使用的确切依赖项的维护者提供报酬。 [了解更多。](https://tidelift.com/subscription/pkg/npm-qs?utm_source=npm-qs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) ## 鸣谢 qs logo 由 [NUMI](https://github.com/numi-hq/open-design) 提供: [NUMI Logo](https://numi.tech/?ref=qs)
标签:CMS安全, GNU通用公共许可证, JavaScript, MITM代理, Node.js, 数据可视化, 数据序列化, 查询字符串, 自定义脚本, 解析库