decode static method

Map<String, dynamic> decode(
  1. dynamic input, [
  2. DecodeOptions? options
])

Decode a query string or a pre-parsed map into a structured map.

  • input may be:
    • a query String (e.g. "a=1&b[c]=2"), or
    • a pre-tokenized Map<String, dynamic> produced by a custom tokenizer.
  • When input is null or the empty string, {} is returned.
  • If DecodeOptions.parseLists is true and the number of top‑level parameters exceeds DecodeOptions.listLimit, list parsing is temporarily disabled for this call to bound memory (mirrors Node qs).
  • Throws ArgumentError if input is neither a String nor a Map<String, dynamic>.

See DecodeOptions for delimiter, nesting depth, numeric-entity handling, duplicates policy, and other knobs.

Implementation

static Map<String, dynamic> decode(dynamic input, [DecodeOptions? options]) {
  options ??= const DecodeOptions();
  // Default to the library's safe, Node-`qs` compatible settings.

  // Fail fast on unsupported input shapes to avoid ambiguous behavior.
  if (!(input is String? || input is Map<String, dynamic>?)) {
    throw ArgumentError.value(
      input,
      'input',
      'The input must be a String or a Map<String, dynamic>',
    );
  }

  // Normalize `null` / empty string to an empty map.
  if (input?.isEmpty ?? true) {
    return <String, dynamic>{};
  }

  final Map<String, dynamic>? tempObj = input is String
      ? _$Decode._parseQueryStringValues(input, options)
      : input;

  // Guardrail: if the top-level parameter count is large, temporarily disable
  // list parsing to keep memory bounded (matches Node `qs`).
  if (options.parseLists &&
      options.listLimit > 0 &&
      (tempObj?.length ?? 0) > options.listLimit) {
    options = options.copyWith(parseLists: false);
  }

  Map<String, dynamic> obj = {};

  // Merge each parsed key into the accumulator using the same rules as Node `qs`.
  // Iterate over the keys and setup the new object
  if (tempObj?.isNotEmpty ?? false) {
    for (final MapEntry<String, dynamic> entry in tempObj!.entries) {
      final parsed = _$Decode._parseKeys(
          entry.key, entry.value, options, input is String);

      if (obj.isEmpty && parsed is Map<String, dynamic>) {
        obj = parsed; // direct assignment – no merge needed
      } else {
        obj = Utils.merge(obj, parsed, options) as Map<String, dynamic>;
      }
    }
  }

  // Drop undefined/empty leaves to match the reference behavior.
  return Utils.compact(obj);
}