encode static method

String encode(
  1. Object? object, [
  2. EncodeOptions? options
])

Encode a map/iterable into a query string.

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 => Utils.createIndexMap(iterable),
    _ => <String, dynamic>{},
  };

  final List<String> 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 ListFormatGenerator gen = options.listFormat.generator;
    final bool crt = identical(gen, ListFormat.comma.generator) &&
        options.commaRoundTrip == true;

    final encoded = _$Encode._encode(
      obj[key],
      undefined: !obj.containsKey(key),
      prefix: key,
      generateArrayPrefix: gen,
      commaRoundTrip: crt,
      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) {
      for (final e in encoded) {
        if (e != null) keys.add(e as String);
      }
    } else if (encoded != null) {
      keys.add(encoded as String);
    }
  }

  // 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('&#10003;')
      /// 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();
}