getredash/redash · warning · QueryParseError

'fields' needs to be a list.

Error message

'fields' needs to be a list.

What it means

Optional 'fields' in a JSON URL query selects/renames output columns; _run_json_query validates it is a list, and any truthy non-list (string, dict, number) raises QueryParseError("'fields' needs to be a list.").

Source

Thrown at redash/query_runner/json_ds.py:195

        fields = query.get("fields")
        path = query.get("path")

        if "pagination" in query:
            pagination = RequestPagination.from_config(self.configuration, query["pagination"])
        else:
            pagination = None

        if isinstance(request_options.get("auth", None), list):
            request_options["auth"] = tuple(request_options["auth"])
        elif self.configuration.get("username") or self.configuration.get("password"):
            request_options["auth"] = (self.configuration.get("username"), self.configuration.get("password"))

        if method not in ("get", "post"):
            raise QueryParseError("Only GET or POST methods are allowed.")

        if fields and not isinstance(fields, list):
            raise QueryParseError("'fields' needs to be a list.")

        results, error = self._get_all_results(query["url"], method, path, pagination, **request_options)
        return parse_json(results, fields), error

    def _get_all_results(self, url, method, result_path, pagination, **request_options):
        """Get all results from a paginated endpoint."""
        base_url = self.configuration.get("base_url")
        url = urljoin(base_url, url)

        results = []
        has_more = True
        while has_more:
            response, error = self._get_json_response(url, method, **request_options)
            has_more = False

            result = _normalize_json(response, result_path)
            if result:
                results.extend(result)

View on GitHub (pinned to ca79fe988d)

Solutions

  1. Wrap values in a list: fields: [id, name] or block-style '-' items
  2. For renaming, use list-of-mappings pairs like fields: [{name: newName}] as documented by the runner

Example fix

# before
fields: name
# after
fields: [name]
Defensive patterns

Strategy: type-guard

Validate before calling

def fields_ok(q: dict) -> bool:
    f = q.get("fields")
    return f is None or isinstance(f, list)

Type guard

import yaml

def is_valid_json_ds_query(q: str) -> bool:
    try:
        parsed = yaml.safe_load(q)
    except yaml.YAMLError:
        return False
    if not isinstance(parsed, dict) or "url" not in parsed:
        return False
    f = parsed.get("fields")
    if f is not None and not isinstance(f, list):
        return False
    return True

Prevention

When it happens

Trigger: Writing fields: name (a YAML string) or fields: {name: name} instead of a sequence; note that a single-element flow-style value must still be a list (fields: [name]).

Common situations: Users writing a single field without brackets, or mapping old/new names as a dict instead of pairs inside a list.

Related errors


AI-assisted analysis of getredash/redash@ca79fe988d (2026-08-28). Data as JSON: /api/errors/917045aa799cd25a. Report an issue: GitHub.