https://github.com/techouse/qs
A query string encoding and decoding library for Dart. Ported from qs for JavaScript.
https://github.com/techouse/qs
dart qs query-encoding query-parser query-string url-parsing url-query
Last synced: about 1 month ago
JSON representation
A query string encoding and decoding library for Dart. Ported from qs for JavaScript.
- Host: GitHub
- URL: https://github.com/techouse/qs
- Owner: techouse
- License: bsd-3-clause
- Created: 2024-04-01T23:14:00.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-03-05T19:48:13.000Z (3 months ago)
- Last Synced: 2025-04-17T14:36:54.476Z (about 1 month ago)
- Topics: dart, qs, query-encoding, query-parser, query-string, url-parsing, url-query
- Language: Dart
- Homepage: https://techouse.github.io/qs/
- Size: 457 KB
- Stars: 6
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE-OF-CONDUCT.md
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.md
Awesome Lists containing this project
README
# qs_dart
A query string encoding and decoding library for Dart.
Ported from [qs](https://www.npmjs.com/package/qs) for JavaScript.
[](https://pub.dev/packages/qs_dart)
[](https://pub.dev/publishers/tusar.dev/packages)
[](https://pub.dev/packages/qs_dart/score)
[](https://pub.dev/packages/qs_dart/score)
[](https://pub.dev/packages/qs_dart/score)
[](https://pubstats.dev/packages/qs_dart)
[](https://pubstats.dev/packages/qs_dart)
[](https://pubstats.dev/packages/qs_dart)
[](https://github.com/techouse/qs/actions/workflows/test.yml)
[](https://codecov.io/gh/techouse/qs)
[](https://app.codacy.com/gh/techouse/qs/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[](LICENSE)
[](https://github.com/sponsors/techouse)
[](https://github.com/techouse/qs/stargazers)## Usage
A simple usage example:
```dart
import 'package:qs_dart/qs_dart.dart';
import 'package:test/test.dart';void main() {
test('Simple example', () {
expect(
QS.decode('a=c'),
equals({'a': 'c'}),
);expect(
QS.encode({'a': 'c'}),
equals('a=c'),
);
});
test('Uri Extension usage', () {
expect(
Uri.parse('https://test.local/example?a[b][c]=d').queryParametersQs(),
equals({
'a': {
'b': {'c': 'd'}
}
}),
);
expect(
Uri.https('test.local', '/example', {'a': '1', 'b': '2'}).toStringQs(),
equals('https://test.local/example?a=1&b=2'),
);
});
}
```### Decoding Maps
```dart
Map decode(
dynamic str, [
DecodeOptions options = const DecodeOptions(),
]);
```[decode] allows you to create nested [Map]s within your query strings, by surrounding the name of sub-keys with
square brackets `[]`. For example, the string `'foo[bar]=baz'` converts to:```dart
expect(
QS.decode('foo[bar]=baz'),
equals({'foo': {'bar': 'baz'}}),
);
```URI encoded strings work too:
```dart
expect(
QS.decode('a%5Bb%5D=c'),
equals({'a': {'b': 'c'}}),
);
```You can also nest your [Map]s, like `'foo[bar][baz]=foobarbaz'`:
```dart
expect(
QS.decode('foo[bar][baz]=foobarbaz'),
equals({'foo': {'bar': {'baz': 'foobarbaz'}}}),
);
```By default, when nesting [Map]s [decode] will only decode up to 5 children deep. This means if you attempt to decode
a string like `'a[b][c][d][e][f][g][h][i]=j'` your resulting [Map] will be:```dart
expect(
QS.decode('a[b][c][d][e][f][g][h][i]=j'),
equals({
'a': {
'b': {
'c': {
'd': {
'e': {
'f': {
'[g][h][i]': 'j'
}
}
}
}
}
}
}),
);
```This depth can be overridden by passing a depth option to [DecodeOptions.depth]:
```dart
expect(
QS.decode(
'a[b][c][d][e][f][g][h][i]=j',
const DecodeOptions(depth: 1),
),
equals({
'a': {
'b': {'[c][d][e][f][g][h][i]': 'j'},
},
}),
);
```You can configure [decode] to throw an error when parsing nested input beyond this depth using
[DecodeOptions.strictDepth] (defaults to false):```dart
expect(
() => QS.decode(
'a[b][c][d][e][f][g][h][i]=j',
const DecodeOptions(
depth: 1,
strictDepth: true,
),
),
throwsA(isA()),
);
```The depth limit helps mitigate abuse when [decode] is used to parse user input, and it is recommended to keep it a
reasonably small number. [DecodeOptions.strictDepth] adds a layer of protection by throwing a [RangeError] when the
limit is exceeded, allowing you to catch and handle such cases.For similar reasons, by default [decode] will only parse up to **1000** parameters. This can be overridden by passing
a [DecodeOptions.parameterLimit] option:```dart
expect(
QS.decode(
'a=b&c=d',
const DecodeOptions(parameterLimit: 1),
),
equals({'a': 'b'}),
);
```To bypass the leading question mark, use [DecodeOptions.ignoreQueryPrefix]:
```dart
expect(
QS.decode(
'?a=b&c=d',
const DecodeOptions(ignoreQueryPrefix: true),
),
equals(
{'a': 'b', 'c': 'd'},
),
);
```An optional [DecodeOptions.delimiter] can also be passed:
```dart
expect(
QS.decode(
'a=b;c=d',
const DecodeOptions(delimiter: ';'),
),
equals({'a': 'b', 'c': 'd'}),
);
```[DecodeOptions.delimiter] can be a [RegExp] too:
```dart
expect(
QS.decode(
'a=b;c=d',
DecodeOptions(delimiter: RegExp(r'[;,]')),
),
equals({'a': 'b', 'c': 'd'}),
);
```Option [DecodeOptions.allowDots] can be used to enable dot notation:
```dart
expect(
QS.decode(
'a.b=c',
const DecodeOptions(allowDots: true),
),
equals({'a': {'b': 'c'}}),
);
```Option [DecodeOptions.decodeDotInKeys] can be used to decode dots in keys
**Note:** it implies [DecodeOptions.allowDots], so [decode] will error if you set [DecodeOptions.decodeDotInKeys]
to `true`, and `DecodeOptions.allowDots` to `false`.```dart
expect(
QS.decode(
'name%252Eobj.first=John&name%252Eobj.last=Doe',
const DecodeOptions(decodeDotInKeys: true),
),
equals({
'name.obj': {'first': 'John', 'last': 'Doe'}
}),
);
```Option [DecodeOptions.allowEmptyLists] can be used to allow empty [List] values in a [Map].
```dart
expect(
QS.decode(
'foo[]&bar=baz',
const DecodeOptions(allowEmptyLists: true),
),
equals({
'foo': [],
'bar': 'baz',
}),
);
```Option [DecodeOptions.duplicates] can be used to change the behavior when duplicate keys are encountered.
```dart
expect(
QS.decode('foo=bar&foo=baz'),
equals({
'foo': ['bar', 'baz']
}),
);expect(
QS.decode(
'foo=bar&foo=baz',
const DecodeOptions(duplicates: Duplicates.combine),
),
equals({
'foo': ['bar', 'baz']
}),
);expect(
QS.decode(
'foo=bar&foo=baz',
const DecodeOptions(duplicates: Duplicates.first),
),
equals({'foo': 'bar'}),
);expect(
QS.decode(
'foo=bar&foo=baz',
const DecodeOptions(duplicates: Duplicates.last),
),
equals({'foo': 'baz'}),
);
```If you have to deal with legacy browsers or services, there's also support for decoding percent-encoded octets as
[latin1]:```dart
expect(
QS.decode(
'a=%A7',
const DecodeOptions(charset: latin1),
),
equals({'a': '§'}),
);
```Some services add an initial `utf8=✓` value to forms so that old Internet Explorer versions are more likely to submit the
form as utf-8. Additionally, the server can check the value against wrong encodings of the checkmark character and detect
that a query string or `application/x-www-form-urlencoded` body was *not* sent as utf-8, eg. if the form had an
`accept-charset` parameter or the containing page had a different character set.**QS** supports this mechanism via the [DecodeOptions.charsetSentinel] option.
If specified, the `utf8` parameter will be omitted from the returned [Map].
It will be used to switch to [latin1] or [utf8] mode depending on how the checkmark is encoded.**Important**: When you specify both the [DecodeOptions.charset] option and the [DecodeOptions.charsetSentinel] option,
the [DecodeOptions.charset] will be overridden when the request contains a `utf8` parameter from which the actual charset
can be deduced. In that sense the [DecodeOptions.charset] will behave as the default charset rather than the authoritative
charset.```dart
expect(
QS.decode(
'utf8=%E2%9C%93&a=%C3%B8',
const DecodeOptions(
charset: latin1,
charsetSentinel: true,
),
),
equals({'a': 'ø'}),
);expect(
QS.decode(
'utf8=%26%2310003%3B&a=%F8',
const DecodeOptions(
charset: utf8,
charsetSentinel: true,
),
),
equals({'a': 'ø'}),
);
```If you want to decode the [`...;`](https://www.w3schools.com/html/html_entities.asp) syntax to the actual character,
you can specify the [DecodeOptions.interpretNumericEntities] option as well:```dart
expect(
QS.decode(
'a=%26%239786%3B',
const DecodeOptions(
charset: latin1,
interpretNumericEntities: true,
),
),
equals({'a': '☺'}),
);
```It also works when the charset has been detected in [DecodeOptions.charsetSentinel] mode.
### Decoding Lists
[decode] can also decode [List]s using a similar `[]` notation:
```dart
expect(
QS.decode('a[]=b&a[]=c'),
equals({
'a': ['b', 'c']
}),
);
```You may specify an index as well:
```dart
expect(
QS.decode('a[1]=c&a[0]=b'),
equals({
'a': ['b', 'c']
}),
);
```Note that the only difference between an index in a [List] and a key in a [Map] is that the value between the brackets
must be a number to create a [List]. When creating [List]s with specific indices, [decode] will compact a sparse
[List] to only the existing values preserving their order:```dart
expect(
QS.decode('a[1]=b&a[15]=c'),
equals({
'a': ['b', 'c']
}),
);
```Note that an empty string is also a value, and will be preserved:
```dart
expect(
QS.decode('a[]=&a[]=b'),
equals({
'a': ['', 'b']
}),
);
expect(
QS.decode('a[0]=b&a[1]=&a[2]=c'),
equals({
'a': ['b', '', 'c']
}),
);
```[decode] will also limit specifying indices in a [List] to a maximum index of **20**.
Any [List] members with an index of greater than **20** will instead be converted to a [Map] with the index as the key.
This is needed to handle cases when someone sent, for example, `a[999999999]` and it will take significant time to iterate
over this huge [List].```dart
expect(
QS.decode('a[100]=b'),
equals({
'a': {'100': 'b'}
}),
);
```This limit can be overridden by passing an [DecodeOptions.listLimit] option:
```dart
expect(
QS.decode(
'a[1]=b',
const DecodeOptions(listLimit: 0),
),
equals({
'a': {'1': 'b'}
}),
);
```To disable List parsing entirely, set [DecodeOptions.parseLists] to `false`.
```dart
expect(
QS.decode(
'a[]=b',
const DecodeOptions(parseLists: false),
),
equals({
'a': {'0': 'b'}
}),
);
```If you mix notations, [decode] will merge the two items into a [Map]:
```dart
expect(
QS.decode('a[0]=b&a[b]=c'),
equals({
'a': {'0': 'b', 'b': 'c'}
}),
);
```You can also create [List]s of [Map]s:
```dart
expect(
QS.decode('a[][b]=c'),
equals({
'a': [
{'b': 'c'}
]
}),
);
```Some people use commas to join [List]s, [decode] can parse it by setting the [DecodeOptions.comma] option to `true`:
```dart
expect(
QS.decode(
'a=b,c',
const DecodeOptions(comma: true),
),
equals({
'a': ['b', 'c']
}),
);
```(_[decode] cannot convert nested [Map]s, such as `'a={b:1},{c:d}'`_)
### Decoding primitive/scalar values (`num`, `bool`, `null`, etc.)
By default, all values are parsed as [String]s.
```dart
expect(
QS.decode('a=15&b=true&c=null'),
equals({
'a': '15',
'b': 'true',
'c': 'null',
}),
);
```### Encoding
```dart
String encode(
Object? object, [
EncodeOptions options = const EncodeOptions(),
]);
```[encode] will by default URI encode the output. [Map]s are stringified as you would expect:
```dart
expect(
QS.encode({'a': 'b'}),
equals('a=b'),
);
expect(
QS.encode({'a': {'b': 'c'}}),
equals('a%5Bb%5D=c'),
);
```This encoding can be disabled by setting the [EncodeOptions.encode] option to `false`:
```dart
expect(
QS.encode(
{
'a': {'b': 'c'}
},
const EncodeOptions(encode: false),
),
equals('a[b]=c'),
);
```Encoding can be disabled for keys by setting the [EncodeOptions.encodeValuesOnly] option to `true`:
```dart
expect(
QS.encode(
{
'a': 'b',
'c': ['d', 'e=f'],
'f': [
['g'],
['h']
]
},
const EncodeOptions(encodeValuesOnly: true),
),
equals('a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'),
);
```This encoding can also be replaced by a custom [Encoder] set as [EncodeOptions.encoder] option:
```dart
expect(
QS.encode(
{
'a': {'b': 'č'}
},
EncodeOptions(
encoder: (
str, {
Encoding? charset,
Format? format,
}) =>
switch (str) {
'č' => 'c',
_ => str,
},
),
),
equals('a[b]=c'),
);
```_(Note: the [EncodeOptions.encoder] option does not apply if [EncodeOptions.encode] is `false`)_
Similar to [EncodeOptions.encoder] there is a [DecodeOptions.decoder] option for [decode] to override decoding of
properties and values:```dart
expect(
QS.decode(
'foo=123',
DecodeOptions(
decoder: (String? str, {Encoding? charset}) =>
num.tryParse(str ?? '') ?? str,
),
),
equals({'foo': 123}),
);
```Examples beyond this point will be shown as though the output is not URI encoded for clarity.
Please note that the return values in these cases *will* be URI encoded during real usage.When [List]s are encoded, they follow the [EncodeOptions.listFormat] option, which defaults to [ListFormat.indices]:
```dart
expect(
QS.encode(
{
'a': ['b', 'c', 'd']
},
const EncodeOptions(encode: false),
),
equals('a[0]=b&a[1]=c&a[2]=d'),
);
```You may override this by setting the [EncodeOptions.indices] option to `false`, or to be more explicit, the
[EncodeOptions.listFormat] option to [ListFormat.repeat]:```dart
expect(
QS.encode(
{
'a': ['b', 'c', 'd']
},
const EncodeOptions(
encode: false,
indices: false,
),
),
equals('a=b&a=c&a=d'),
);
```You may use the [EncodeOptions.listFormat] option to specify the format of the output [List]:
```dart
expect(
QS.encode(
{
'a': ['b', 'c']
},
const EncodeOptions(
encode: false,
listFormat: ListFormat.indices,
),
),
equals('a[0]=b&a[1]=c'),
);expect(
QS.encode(
{
'a': ['b', 'c']
},
const EncodeOptions(
encode: false,
listFormat: ListFormat.brackets,
),
),
equals('a[]=b&a[]=c'),
);expect(
QS.encode(
{
'a': ['b', 'c']
},
const EncodeOptions(
encode: false,
listFormat: ListFormat.repeat,
),
),
equals('a=b&a=c'),
);expect(
QS.encode(
{
'a': ['b', 'c']
},
const EncodeOptions(
encode: false,
listFormat: ListFormat.comma,
),
),
equals('a=b,c'),
);
```**Note:** When using [EncodeOptions.listFormat] set to [ListFormat.comma], you can also pass the [EncodeOptions.commaRoundTrip]
option set to `true` or `false`, to append `[]` on single-item [List]s, so that they can round trip through a parse.When [Map]s are encoded, by default they use bracket notation:
```dart
expect(
QS.encode(
{
'a': {
'b': {'c': 'd', 'e': 'f'}
}
},
const EncodeOptions(encode: false),
),
equals('a[b][c]=d&a[b][e]=f'),
);
```You may override this to use dot notation by setting the [EncodeOptions.allowDots] option to `true`:
```dart
expect(
QS.encode(
{
'a': {
'b': {'c': 'd', 'e': 'f'}
}
},
const EncodeOptions(
encode: false,
allowDots: true,
),
),
equals('a.b.c=d&a.b.e=f'),
);
```You may encode the dot notation in the keys of [Map] with option [EncodeOptions.encodeDotInKeys] by setting it to `true`:
```dart
expect(
QS.encode(
{
'name.obj': {'first': 'John', 'last': 'Doe'}
},
const EncodeOptions(
allowDots: true,
encodeDotInKeys: true,
),
),
equals('name%252Eobj.first=John&name%252Eobj.last=Doe'),
);
```**Caveat:** when [EncodeOptions.encodeValuesOnly] is `true` as well as [EncodeOptions.encodeDotInKeys], only dots in
keys and nothing else will be encoded.You may allow empty [List] values by setting the [EncodeOptions.allowEmptyLists] option to `true`:
```dart
expect(
QS.encode(
{
'foo': [],
'bar': 'baz',
},
const EncodeOptions(
encode: false,
allowEmptyLists: true,
),
),
equals('foo[]&bar=baz'),
);
```Empty strings and null values will omit the value, but the equals sign (`=`) remains in place:
```dart
expect(
QS.encode(
{
'a': '',
},
),
equals('a='),
);
```Key with no values (such as an empty [Map] or [List]) will return nothing:
```dart
expect(
QS.encode(
{
'a': [],
},
),
equals(''),
);expect(
QS.encode(
{
'a': {},
},
),
equals(''),
);expect(
QS.encode(
{
'a': [{}],
},
),
equals('')
);expect(
QS.encode(
{
'a': {'b': []},
},
),
equals('')
);expect(
QS.encode(
{
'a': {'b': {}},
},
),
equals('')
);
```Properties that are [Undefined] will be omitted entirely:
```dart
expect(
QS.encode(
{
'a': null,
'b': const Undefined(),
},
),
equals('a='),
);
```The query string may optionally be prepended with a question mark:
```dart
expect(
QS.encode(
{
'a': 'b',
'c': 'd',
},
const EncodeOptions(addQueryPrefix: true),
),
equals('?a=b&c=d'),
);
```The delimiter may be overridden as well:
```dart
expect(
QS.encode(
{
'a': 'b',
'c': 'd',
},
const EncodeOptions(delimiter: ';'),
),
equals('a=b;c=d'),
);
```If you only want to override the serialization of [DateTime] objects, you can provide a custom [DateSerializer] in the
[EncodeOptions.serializeDate] option:```dart
expect(
QS.encode(
{
'a': DateTime.fromMillisecondsSinceEpoch(7).toUtc(),
},
const EncodeOptions(encode: false),
),
equals('a=1970-01-01T00:00:00.007Z'),
);
expect(
QS.encode(
{
'a': DateTime.fromMillisecondsSinceEpoch(7).toUtc(),
},
EncodeOptions(
encode: false,
serializeDate: (DateTime date) =>
date.millisecondsSinceEpoch.toString(),
),
),
equals('a=7'),
);
```You may use the [EncodeOptions.sort] option to affect the order of parameter keys:
```dart
expect(
QS.encode(
{
'a': 'c',
'z': 'y',
'b': 'f',
},
EncodeOptions(
encode: false,
sort: (a, b) => a.compareTo(b),
),
),
equals('a=c&b=f&z=y'),
);
```Finally, you can use the [EncodeOptions.filter] option to restrict which keys will be included in the encoded output.
If you pass a [Function], it will be called for each key to obtain the replacement value.
Otherwise, if you pass a [List], it will be used to select properties and [List] indices to be encoded:```dart
expect(
QS.encode(
{
'a': 'b',
'c': 'd',
'e': {
'f': DateTime.fromMillisecondsSinceEpoch(123),
'g': [2],
},
},
EncodeOptions(
encode: false,
filter: (prefix, value) => switch (prefix) {
'b' => const Undefined(),
'e[f]' => (value as DateTime).millisecondsSinceEpoch,
'e[g][0]' => (value as num) * 2,
_ => value,
},
),
),
equals('a=b&c=d&e[f]=123&e[g][0]=4'),
);expect(
QS.encode(
{
'a': 'b',
'c': 'd',
'e': 'f',
},
const EncodeOptions(
encode: false,
filter: ['a', 'e'],
),
),
equals('a=b&e=f'),
);expect(
QS.encode(
{
'a': ['b', 'c', 'd'],
'e': 'f',
},
const EncodeOptions(
encode: false,
filter: ['a', 0, 2],
),
),
equals('a[0]=b&a[2]=d'),
);
```### Handling of `null` values
By default, `null` values are treated like empty strings:
```dart
expect(
QS.encode(
{
'a': null,
'b': '',
},
),
equals('a=&b='),
);
```Decoding does not distinguish between parameters with and without equal signs.
Both are converted to empty strings.```dart
expect(
QS.decode('a&b='),
equals({
'a': '',
'b': '',
}),
);
```To distinguish between `null` values and empty [String]s use the [EncodeOptions.strictNullHandling] flag.
In the result string the `null` values have no `=` sign:```dart
expect(
QS.encode(
{
'a': null,
'b': '',
},
const EncodeOptions(strictNullHandling: true),
),
equals('a&b='),
);
```To decode values without `=` back to `null` use the [DecodeOptions.strictNullHandling] flag:
```dart
expect(
QS.decode(
'a&b=',
const DecodeOptions(strictNullHandling: true),
),
equals({
'a': null,
'b': '',
}),
);
```To completely skip rendering keys with `null` values, use the [EncodeOptions.skipNulls] flag:
```dart
expect(
QS.encode(
{
'a': 'b',
'c': null,
},
const EncodeOptions(skipNulls: true),
),
equals('a=b'),
);
```If you're communicating with legacy systems, you can switch to [latin1] using the [EncodeOptions.charset] option:
```dart
expect(
QS.encode(
{
'æ': 'æ',
},
const EncodeOptions(charset: latin1),
),
equals('%E6=%E6'),
);
```Characters that don't exist in [latin1] will be converted to numeric entities, similar to what browsers do:
```dart
expect(
QS.encode(
{
'a': '☺',
},
const EncodeOptions(charset: latin1),
),
equals('a=%26%239786%3B'),
);
```You can use the [EncodeOptions.charsetSentinel] option to announce the character by including an `utf8=✓` parameter with
the proper encoding of the checkmark, similar to what Ruby on Rails and others do when submitting forms.```dart
expect(
QS.encode(
{
'a': '☺',
},
const EncodeOptions(charsetSentinel: true),
),
equals('utf8=%E2%9C%93&a=%E2%98%BA'),
);
expect(
QS.encode(
{
'a': 'æ',
},
const EncodeOptions(
charset: latin1,
charsetSentinel: true,
),
),
equals('utf8=%26%2310003%3B&a=%E6'),
);
```### Dealing with special character sets
By default, the encoding and decoding of characters is done in [utf8], and [latin1] support is also built in via
the [EncodeOptions.charset] and [DecodeOptions.charset] parameter, respectively.If you wish to encode query strings to a different character set (i.e.
[Shift JIS](https://en.wikipedia.org/wiki/Shift_JIS)) you can use the [euc](https://pub.dev/packages/euc) package```dart
expect(
QS.encode(
{
'a': 'こんにちは!',
},
EncodeOptions(
encoder: (str, {Encoding? charset, Format? format}) {
if ((str as String?)?.isNotEmpty ?? false) {
final Uint8List buf = Uint8List.fromList(
ShiftJIS().encode(str!),
);
final List result = [
for (int i = 0; i < buf.length; ++i) buf[i].toRadixString(16)
];
return '%${result.join('%')}';
}
return '';
},
),
),
equals('%61=%82%b1%82%f1%82%c9%82%bf%82%cd%81%49'),
);
```This also works for decoding of query strings:
```dart
expect(
QS.decode(
'%61=%82%b1%82%f1%82%c9%82%bf%82%cd%81%49',
DecodeOptions(
decoder: (str, {Encoding? charset}) {
if (str == null) {
return null;
}final RegExp reg = RegExp(r'%([0-9A-F]{2})', caseSensitive: false);
final List result = [];
Match? parts;
while ((parts = reg.firstMatch(str!)) != null && parts != null) {
result.add(int.parse(parts.group(1)!, radix: 16));
str = str.substring(parts.end);
}
return ShiftJIS().decode(
Uint8List.fromList(result),
);
},
),
),
equals({
'a': 'こんにちは!',
}),
);
```### RFC 3986 and RFC 1738 space encoding
The default [EncodeOptions.format] is [Format.rfc3986] which encodes `' '` to `%20` which is backward compatible.
You can also set the [EncodeOptions.format] to [Format.rfc1738] which encodes `' '` to `+`.```dart
expect(
QS.encode(
{
'a': 'b c',
},
),
equals('a=b%20c'),
);expect(
QS.encode(
{
'a': 'b c',
},
const EncodeOptions(format: Format.rfc3986),
),
equals('a=b%20c'),
);expect(
QS.encode(
{
'a': 'b c',
},
const EncodeOptions(format: Format.rfc1738),
),
equals('a=b+c'),
);
```---
Special thanks to the authors of [qs](https://www.npmjs.com/package/qs) for JavaScript:
- [Jordan Harband](https://github.com/ljharb)
- [TJ Holowaychuk](https://github.com/visionmedia/node-querystring)