encode static method
- Object? object, [
- EncodeOptions? options
Encode a map/iterable into a query string.
object
may be:- a
Map<String, dynamic>
(encoded as key/value pairs), - an
Iterable
(encoded as an index‑keyed map:0
,1
, …), or null
(returns the empty string).
- a
- If EncodeOptions.filter is a function, it is invoked like the
Node
qs
filter; if it's an iterable, it specifies the exact key order/selection. - Keys are optionally sorted via EncodeOptions.sort.
- EncodeOptions.addQueryPrefix and EncodeOptions.charsetSentinel
control the leading
?
and sentinel token emission.
See EncodeOptions for details about list formats, output format, and hooks.
Implementation
static String encode(Object? object, [EncodeOptions? options]) {
options ??= const EncodeOptions();
// Use default encoding settings unless overridden by the caller.
// Normalize supported inputs into a mutable map we can traverse.
Map<String, dynamic> obj = switch (object) {
Map<String, dynamic> map => {...map},
Iterable iterable => iterable
.toList()
.asMap()
.map((int k, dynamic v) => MapEntry(k.toString(), v)),
_ => <String, dynamic>{},
};
final List keys = [];
// Nothing to encode.
if (obj.isEmpty) {
return '';
}
List? objKeys;
// Support the two `qs` filter forms: function and whitelist iterable.
if (options.filter is Function) {
obj = options.filter?.call('', obj);
} else if (options.filter is Iterable) {
objKeys = List.of(options.filter);
}
objKeys ??= obj.keys.toList();
// Deterministic key order if a sorter is provided.
if (options.sort is Function) {
objKeys.sort(options.sort);
}
// Internal side-channel used by the encoder to detect cycles and share state.
final WeakMap sideChannel = WeakMap();
for (int i = 0; i < objKeys.length; i++) {
final key = objKeys[i];
if (key is! String? || (obj[key] == null && options.skipNulls)) {
continue;
}
final encoded = _$Encode._encode(
obj[key],
undefined: !obj.containsKey(key),
prefix: key,
generateArrayPrefix: options.listFormat.generator,
commaRoundTrip:
options.listFormat.generator == ListFormat.comma.generator &&
options.commaRoundTrip == true,
allowEmptyLists: options.allowEmptyLists,
strictNullHandling: options.strictNullHandling,
skipNulls: options.skipNulls,
encodeDotInKeys: options.encodeDotInKeys,
encoder: options.encode ? options.encoder : null,
serializeDate: options.serializeDate,
filter: options.filter,
sort: options.sort,
allowDots: options.allowDots,
format: options.format,
formatter: options.formatter,
encodeValuesOnly: options.encodeValuesOnly,
charset: options.charset,
addQueryPrefix: options.addQueryPrefix,
sideChannel: sideChannel,
);
if (encoded is Iterable) {
keys.addAll(encoded);
} else {
keys.add(encoded);
}
}
// Join all encoded segments with the chosen delimiter.
final String joined = keys.join(options.delimiter);
final StringBuffer out = StringBuffer();
if (options.addQueryPrefix) {
out.write('?');
}
// Optionally emit the charset sentinel (mirrors Node `qs`).
if (options.charsetSentinel) {
out.write(switch (options.charset) {
/// encodeURIComponent('✓')
/// the "numeric entity" representation of a checkmark
latin1 => '${Sentinel.iso}&',
/// encodeURIComponent('✓')
utf8 => '${Sentinel.charset}&',
_ => '',
});
}
// Append the payload after any optional prefix/sentinel.
if (joined.isNotEmpty) {
out.write(joined);
}
return out.toString();
}