neoqs

neoqs

现代化的轻量级查询字符串解析库

neoqs是qs的TypeScript重写版本,提供现代化、轻量级且完全兼容的查询字符串解析和生成功能。该库支持零依赖、ESM优先设计,保持与qs相同的API。neoqs能够解析嵌套对象和数组,同时提供深度限制和参数数量限制等安全选项。此外,neoqs还提供legacy build以兼容旧版浏览器和Node环境。

neoqs查询字符串解析字符串化TypeScriptGithub开源项目

neoqs

一个具有额外安全功能的查询字符串解析和字符串化库。它是 qs 的一个分支和 TypeScript 重写版本,旨在成为现代、轻量级但完全向后兼容 qs 的库。

主要维护者:Puru Vijay

qs 模块最初由 TJ Holowaychuk 创建和维护。

neoqs 未得到 qs 当前维护者的认可。


  • 🤌 3.9KB min+brotli(比 qs 小 3 倍)
  • 🚥 零依赖
  • 🎹 TypeScript。摒弃 @types/qs
  • ❎ 无 polyfills
  • 🛸 ESM 优先
  • 📜 支持 ES5 的传统模式

本包旨在遵循以下规则,并将长期坚持:

  • 无依赖。
  • 无 polyfills。
  • ESM 优先。
  • 力求现代化
  • 始终提供传统模式
  • 始终遵循 qs API。已经有许多包做到了这一点。neoqs 旨在成为 qs 的直接替代品,提供相同的 API,但零依赖并改善开发者体验。

何时使用本包?

本包旨在成为 qs 的直接替代品,提供相同的 API,但零依赖并改善开发者体验。因此,如果你的项目中已经在使用 qs,你应该使用本包来替代。

何时使用本包?

如果你的用例非常简单(foo=bar&baz=baka),主要是顶层键(foo=bar),且不需要支持非常旧的浏览器和 Node 版本,那么从你的项目中删除 qsneoqs,改用 URLSearchParams

使用哪个构建版本?

neoqs 提供两个构建版本:

  • 默认版:向后兼容 qs 并提供相同的 API,但仅支持 ESM,编译为 ES2022,适用于 Node 18+
  • 传统版:ES5 和 CommonJS 的传统构建,兼容 qs 并提供相同的 API。理论上可以支持到 Node 4.0.0,但未经测试。

以下是不同构建版本的对比矩阵:

构建版本ESMCJS浏览器NodePolyfills大小
默认版✅ ES20223.9KB min+brotli
传统版✅ ES54.2KB min+brotli

如果你:

正在发布一个支持 CommonJS 的库:

使用 传统版 构建以兼容旧浏览器和 Node 版本。

const { parse, stringify } = require('neoqs/legacy');

ESM:

import { parse, stringify } from 'neoqs/legacy';

不关心旧浏览器或 Node 版本:

使用默认构建以避免破坏性变更,并获得更好的开发者体验。

import * as qs from 'neoqs'; const obj = qs.parse('a=c'); console.log(obj); const str = qs.stringify(obj); console.log(str);

关心旧浏览器或 Node 或 CommonJS 版本:

使用传统构建以兼容旧浏览器和 Node 版本。

const { parse, stringify } = require('neoqs/legacy');

ESM:

import { parse, stringify } from 'neoqs/legacy';

使用方法

import * as qs from 'neoqs'; const obj = qs.parse('a=c'); console.log(obj); const str = qs.stringify(obj); console.log(str);

解析对象

parse(string, [options]);

qs 允许你在查询字符串中创建嵌套对象,方法是用方括号 [] 包围子键的名称。例如,字符串 'foo[bar]=baz' 会转换为:

console.log(qs.parse('foo[bar]=baz')); // 输出: // { // foo: { // bar: 'baz' // } //});

当使用 plainObjects 选项时,解析后的值会以空对象的形式返回,通过 Object.create(null) 创建,因此你应该注意,原型方法不会存在于其上,用户可以将这些名称设置为任何他们喜欢的值:

const nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true }); console.log(nullObject); // 输出: // { // a: { // hasOwnProperty: 'b' // } // }

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

const protoObject = qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true }); console.log(protoObject); // 输出: // { // a: { // hasOwnProperty: 'b' // } // }

URI 编码的字符串也可以正常工作:

console.log(qs.parse('a%5Bb%5D=c')); // 输出: // { // a: { // b: 'c' // } // }

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

console.log(qs.parse('foo[bar][baz]=foobarbaz')); // 输出: // { // foo: { // bar: { // baz: 'foobarbaz' // } // } // }

默认情况下,在嵌套对象时,qs 最多只会解析 5 层深度。这意味着如果你尝试解析像 'a[b][c][d][e][f][g][h][i]=j' 这样的字符串,你得到的对象将是:

const expected = { a: { b: { c: { d: { e: { f: { '[g][h][i]': 'j' } } } } } } }; console.log(qs.stringify(expected)); // 输出: // 'a[b][c][d][e][f][g][h][i]=j'

可以通过向 qs.parse(string, [options]) 传递 depth 选项来覆盖这个深度:

const deep = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 }); console.log(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 选项来覆盖这个限制:

const limited = qs.parse('a=b&c=d', { parameterLimit: 1 }); console.log(limited); // 输出: // { // a: 'b' // }

要忽略开头的问号,可以使用 ignoreQueryPrefix

const prefixed = qs.parse('?a=b&c=d', { ignoreQueryPrefix: true }); console.log(prefixed); // 输出: // { // a: 'b', // c: 'd' // }

还可以传递一个可选的分隔符:

const delimited = qs.parse('a=b;c=d', { delimiter: ';' }); console.log(delimited); // 输出: // { // a: 'b', // c: 'd' // }

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

const regexed = qs.parse('a=b;c=d,e=f', { delimiter: /[;,]/ }); console.log(regexed); // 输出: // { // a: 'b', // c: 'd', // e: 'f' // }

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

const withDots = qs.parse('a.b=c', { allowDots: true }); console.log(withDots); // 输出: // { // a: { // b: 'c' // } // }

可以使用 decodeDotInKeys 选项来解码键中的点号。注意:这隐含了 allowDots,所以如果你将 decodeDotInKeys 设置为 true,而 allowDots 设置为 falseparse 将会报错。

const withDots = qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { decodeDotInKeys: true }); console.log(withDots); // 输出: // { // 'name.obj': { // first: 'John', // last: 'Doe' // } // }

可以使用allowEmptyArrays选项来允许对象中的空数组值

const withEmptyArrays = qs.parse('foo[]&bar=baz', { allowEmptyArrays: true }); console.log(withEmptyArrays); // 输出: // { // foo: [], // bar: 'baz' // }

可以使用duplicates选项来更改遇到重复键时的行为

console.log(qs.parse('foo=bar&foo=baz', { duplicates: 'combine' })); // 输出: // { // foo: ['bar', 'baz'] // } console.log(qs.parse('foo=bar&foo=baz', { duplicates: 'first' })); // 输出: // { // foo: 'bar' // } console.log(qs.parse('foo=bar&foo=baz', { duplicates: 'last' })); // 输出: // { // foo: 'baz' // } console.log(qs.parse('foo=bar&foo=baz', { duplicates: 'replace' })); // 输出: // { // foo: 'bar' // }

如果您需要处理旧版浏览器或服务,还支持将百分比编码的八进制解码为iso-8859-1:

const oldCharset = qs.parse('a=%A7', { charset: 'iso-8859-1' }); console.log(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选项时,如果请求包含可以推断实际字符集的utf8参数,charset将被覆盖。在这种情况下,charset将作为默认字符集而不是权威字符集。

const detectedAsUtf8 = qs.parse('utf8=%E2%9C%93&a=%C3%B8', { charset: 'iso-8859-1', charsetSentinel: true }); console.log(detectedAsUtf8); // 输出: // { // a: 'ø' // } // 浏览器在以iso-8859-1提交时将勾选标记编码为✓: const detectedAsIso8859_1 = qs.parse('utf8=%26%2310003%3B&a=%F8', { charset: 'utf-8', charsetSentinel: true }); console.log(detectedAsIso8859_1); // 输出: // { // a: 'ø' // }

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

const detectedAsIso8859_1 = qs.parse('a=%26%239786%3B', { charset: 'iso-8859-1', interpretNumericEntities: true }); console.log(detectedAsIso8859_1); // 输出: { // a: '☺' // }

charsetSentinel模式下检测字符集时,它也能正常工作。

解析数组

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

const withArray = qs.parse('a[]=b&a[]=c'); console.log(withArray); // 输出: // { // a: ['b', 'c'] // }

您也可以指定索引:

const withIndexes = qs.parse('a[1]=c&a[0]=b'); console.log(withIndexes); // 输出: // { // a: ['b', 'c'] // }

请注意,数组中的索引和对象中的键之间的唯一区别是方括号之间的值必须是数字才能创建数组。在创建具有特定索引的数组时,qs将压缩稀疏数组,只保留现有值并保持它们的顺序:

const noSparse = qs.parse('a[1]=b&a[15]=c'); console.log(noSparse); // 输出: // { // a: ['b', 'c'] // }

您也可以使用allowSparse选项来解析稀疏数组:

const sparseArray = qs.parse('a[1]=2&a[3]=5', { allowSparse: true }); console.log(sparseArray); // 输出: // { // a: [, '2', , '5'] // }

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

const withEmptyString = qs.parse('a[]=&a[]=b'); console.log(withEmptyString); // 输出: // { // a: ['', 'b'] // } const withIndexedEmptyString = qs.parse('a[0]=b&a[1]=&a[2]=c'); console.log(withIndexedEmptyString); // 输出: // { // a: ['b', '', 'c'] // }

qs还会将数组中指定的索引限制为最大索引20。任何索引大于20的数组成员将转换为以索引为键的对象。这是为了处理有人发送例如a[999999999]的情况,遍历这个巨大的数组将花费大量时间。

const withMaxIndex = qs.parse('a[100]=b'); console.log(withMaxIndex); // 输出: // { // a: { // 100: 'b' // } // }

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

const withArrayLimit = qs.parse('a[1]=b', { arrayLimit: 0 }); console.log(withArrayLimit); // 输出: // { // a: { // 1: 'b' // } // }

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

const noParsingArrays = qs.parse('a[]=b', { parseArrays: false }); console.log(noParsingArrays); // 输出: // { // a: { // 0: 'b' // } // }

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

const mixedNotation = qs.parse('a[0]=b&a[b]=c'); console.log(mixedNotation); // 输出: // { // a: { // 0: 'b', // b: 'c' // } // }

您还可以创建对象数组:

const arraysOfObjects = qs.parse('a[][b]=c'); console.log(arraysOfObjects); // 输出: // { // a: [{ b: 'c' }] // }

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

const arraysOfObjects = qs.parse('a=b,c', { comma: true }); console.log(arraysOfObjects); // 输出: // { // a: ['b', 'c'] // }

这无法转换嵌套对象,例如a={b:1},{c:d}

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

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

const primitiveValues = qs.parse('a=15&b=true&c=null'); console.log(primitiveValues); // 输出: // { // a: '15', // b: 'true', // c: 'null' // }

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

字符串化

qs.stringify(object, [options]);

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

console.log(qs.stringify({ a: 'b' })); // 输出: // a=b console.log(qs.stringify({ a: { b: 'c' } })); // 输出: // a%5Bb%5D=c

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

const unencoded = qs.stringify({ a: { b: 'c' } }, { encode: false }); console.log(unencoded); // 输出: // a[b]=c

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

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

这种编码也可以通过设置encoder选项为自定义编码方法来替换:

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

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

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

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

你可以使用提供给编码器的类型参数,对键和值使用不同的逻辑进行编码:

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

解码器也提供了类型参数:

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

为了清晰起见,此后的示例将展示未经 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 选项设置为 truefalse,以在单项数组上附加 [],使其能够通过解析进行往返传输。

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

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 设置为 falsestringify 将会报错。注意:当 encodeValuesOnlytrueencodeDotInKeys 也为 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 值将省略值,但等号 (=) 保留:

console.log(qs.stringify({ a: '' })); // 输出: // a=

没有值的键(如空对象或数组)将不返回任何内容:

console.log(qs.stringify({ a: [] })); // 输出: // '' console.log(qs.stringify({ a: {} })); // 输出: // '' console.log(qs.stringify({ a: [{}] })); // 输出: // '' console.log(qs.stringify({ a: { b: [] } })); // 输出: // '' console.log(qs.stringify({ a: { b: {} } })); // 输出: // ''

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

console.log(qs.stringify({ a: null, b: undefined })); // 输出: // a=

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

console.log(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true })); // 输出: // ?a=b&c=d

分隔符也可以在 stringify 中被覆盖:

console.log(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' })); // 输出: // a=b;c=d

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

const date = new Date(7); console.log(qs.stringify({ a: date })); // 输出: // a=1970-01-01T00:00:00.007Z console.log( qs.stringify( { a: date }, { serializeDate: function (d) { return d.getTime(); } } ) ); // 输出: // a=7

你可以使用 sort 选项来影响参数键的顺序:

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

最后,你可以使用 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' 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 为用户定义的类型注入自定义序列化。假设你正在使用一个 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}`; } // 以常规方式序列化 return value; } } ); // range=30...70

处理 null

默认情况下,null 值被视为空字符串:

const withNull = qs.stringify({ a: null, b: '' }); console.log(withNull); // 输出: // a=&b=1

解析时不区分有等号和无等号的参数。两者都被转换为空字符串。

const equalsInsensitive = qs.parse('a&b='); console.log(equalsInsensitive); // 输出: // { // a: '', // b: '' // }

要区分 null 值和空字符串,请使用 strictNullHandling 标志。在结果字符串中,null 值没有 = 符号:

const strictNull = qs.stringify({ a: null, b: '' }, { strictNullHandling: true }); console.log(strictNull); // 输出: // a&b=

要将没有=的值解析回null,请使用strictNullHandling标志:

const parsedStrictNull = qs.parse('a&b=', { strictNullHandling: true }); console.log(parsedStrictNull); // 输出: // { // a: null, // b: '' // }

要完全跳过渲染值为null的键,请使用skipNulls标志:

const nullsSkipped = qs.stringify({ a: 'b', c: null }, { skipNulls: true }); console.log(nullsSkipped); // 输出: // a=b

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

const iso = qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }); console.log(iso); // 输出: // %E6=%E6

iso-8859-1中不存在的字符将被转换为数字实体,类似于浏览器的处理方式:

const numeric = qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }); console.log(numeric); // 输出: // a=%26%239786%3B

您可以使用charsetSentinel选项来通过包含一个utf8=✓参数来声明字符集,该参数使用正确的编码方式编码复选标记,类似于Ruby on Rails和其他框架在提交表单时的做法。

const sentinel = qs.stringify({ a: '☺' }, { charsetSentinel: true }); console.log(sentinel); // 输出: // utf8=%E2%9C%93&a=%E2%98%BA const isoSentinel = qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }); console.log(isoSentinel); // 输出: // utf8=%26%2310003%3B&a=%E6

处理特殊字符集

默认情况下,字符的编码和解码是以utf-8进行的,通过charset参数还内置了对iso-8859-1的支持。

如果您希望将查询字符串编码为不同的字符集(例如Shift JIS),可以使用qs-iconv库:

import qsEnvEncoder from 'qs-iconv/encoder'; const encoder = qsEnvEncoder('shift_jis'); const shiftJISEncoded = qs.stringify({ a: 'こんにちは!' }, { encoder: encoder }); console.log(shiftJISEncoded); // 输出: // a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I

这也适用于查询字符串的解码:

import qsEnvDecoder from 'qs-iconv/decoder'; const decoder = qsEnvDecoder('shift_jis'); const obj = qs.parse('a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I', { decoder: decoder }); console.log(obj); // 输出: // { // a: 'こんにちは!' // }

RFC 3986和RFC 1738空格编码

RFC3986作为默认选项使用,将' '编码为_%20_,这是向后兼容的。同时,输出可以根据RFC1738进行字符串化,其中' '等同于'+'。

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

安全性

如果您发现潜在的安全漏洞需要报告,请通过Twitter私信联系@puruvjdev

编辑推荐精选

蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI辅助写作AI工具蛙蛙写作AI写作工具学术助手办公助手营销助手AI助手
Trae

Trae

字节跳动发布的AI编程神器IDE

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

热门AI助手AI对话AI工具聊天机器人
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

热门AI辅助写作AI工具讯飞绘文内容运营AI创作个性化文章多平台分发AI助手
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

下拉加载更多