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