{"record":{"id":"917045aa799cd25a","repo":"getredash/redash","slug":"fields-needs-to-be-a-list","errorCode":null,"errorMessage":"'fields' needs to be a list.","messagePattern":"'fields' needs to be a list\\.","errorType":"validation","errorClass":"QueryParseError","httpStatus":null,"severity":"warning","filePath":"redash/query_runner/json_ds.py","lineNumber":195,"sourceCode":"\n        fields = query.get(\"fields\")\n        path = query.get(\"path\")\n\n        if \"pagination\" in query:\n            pagination = RequestPagination.from_config(self.configuration, query[\"pagination\"])\n        else:\n            pagination = None\n\n        if isinstance(request_options.get(\"auth\", None), list):\n            request_options[\"auth\"] = tuple(request_options[\"auth\"])\n        elif self.configuration.get(\"username\") or self.configuration.get(\"password\"):\n            request_options[\"auth\"] = (self.configuration.get(\"username\"), self.configuration.get(\"password\"))\n\n        if method not in (\"get\", \"post\"):\n            raise QueryParseError(\"Only GET or POST methods are allowed.\")\n\n        if fields and not isinstance(fields, list):\n            raise QueryParseError(\"'fields' needs to be a list.\")\n\n        results, error = self._get_all_results(query[\"url\"], method, path, pagination, **request_options)\n        return parse_json(results, fields), error\n\n    def _get_all_results(self, url, method, result_path, pagination, **request_options):\n        \"\"\"Get all results from a paginated endpoint.\"\"\"\n        base_url = self.configuration.get(\"base_url\")\n        url = urljoin(base_url, url)\n\n        results = []\n        has_more = True\n        while has_more:\n            response, error = self._get_json_response(url, method, **request_options)\n            has_more = False\n\n            result = _normalize_json(response, result_path)\n            if result:\n                results.extend(result)","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/getredash/redash/blob/ca79fe988d81cdac9675b412f3dfcab107bc1fbc/redash/query_runner/json_ds.py#L177-L213","documentation":"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.\").","triggerScenarios":"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]).","commonSituations":"Users writing a single field without brackets, or mapping old/new names as a dict instead of pairs inside a list.","solutions":["Wrap values in a list: fields: [id, name] or block-style '-' items","For renaming, use list-of-mappings pairs like fields: [{name: newName}] as documented by the runner"],"exampleFix":"# before\nfields: name\n# after\nfields: [name]","handlingStrategy":"type-guard","validationCode":"def fields_ok(q: dict) -> bool:\n    f = q.get(\"fields\")\n    return f is None or isinstance(f, list)","typeGuard":"import yaml\n\ndef is_valid_json_ds_query(q: str) -> bool:\n    try:\n        parsed = yaml.safe_load(q)\n    except yaml.YAMLError:\n        return False\n    if not isinstance(parsed, dict) or \"url\" not in parsed:\n        return False\n    f = parsed.get(\"fields\")\n    if f is not None and not isinstance(f, list):\n        return False\n    return True","tryCatchPattern":null,"preventionTips":["Always write fields as a YAML list ([a, b] or '-' items)","Single field still needs brackets: fields: [name]","Lint parsed query types before execution"],"tags":["json-ds","yaml","type-validation"],"backgroundTag":"invalid-query-format","analyzedSha":"ca79fe988d81cdac9675b412f3dfcab107bc1fbc","analyzedAt":"2026-08-28T18:32:34.637Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}