encode static method
- Object? object, [
- EncodeOptions? options
Encode a map/iterable into a query string.
objectmay 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
qsfilter; 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.
options.validate();
// Normalize supported inputs into a mutable map we can traverse.
Map<String, dynamic> obj = switch (object) {
Map<String, dynamic> map => {...map},
Iterable iterable => Utils.createIndexMap(iterable),
_ => <String, dynamic>{},
};
final StringBuffer payload = StringBuffer();
bool hasPayload = false;
final String delimiter = options.delimiter;
void addFragment(String value) {
if (hasPayload) {
payload.write(delimiter);
}
payload.write(value);
hasPayload = true;
}
// 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);
}
// Active-path set used by the encoder for cycle detection across frames.
final Set<Object> sideChannel = HashSet<Object>.identity();
final ListFormatGenerator gen = options.listFormat.generator;
final bool crt = identical(gen, ListFormat.comma.generator) &&
options.commaRoundTrip == true;
final bool ccn = identical(gen, ListFormat.comma.generator) &&
options.commaCompactNulls == true;
final EncodeConfig rootConfig = EncodeConfig(
generateArrayPrefix: gen,
commaRoundTrip: crt,
commaCompactNulls: ccn,
allowEmptyLists: options.allowEmptyLists,
strictNullHandling: options.strictNullHandling,
skipNulls: options.skipNulls,
encodeDotInKeys: options.encodeDotInKeys,
encoder: options.encode ? options.encoder : null,
serializeDate: options.serializeDate,
sort: options.sort,
filter: options.filter,
allowDots: options.allowDots,
format: options.format,
formatter: options.formatter,
encodeValuesOnly: options.encodeValuesOnly,
charset: options.charset,
);
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),
sideChannel: sideChannel,
prefix: key,
rootConfig: rootConfig,
);
if (encoded is Iterable) {
for (final e in encoded) {
if (e != null) {
addFragment(e as String);
}
}
} else if (encoded != null) {
addFragment(encoded as String);
}
}
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 (hasPayload) {
out.write(payload);
}
return out.toString();
}