{"record":{"id":"08c3355fc0ce991b","repo":"makeplane/plane","slug":"invalid-json","errorCode":"invalid_json","errorMessage":"Invalid JSON for '{source_label}'. Expected a valid JSON object.","messagePattern":"Invalid JSON for '(.+?)'\\. Expected a valid JSON object\\.","errorType":"validation","errorClass":"DRFValidationError","httpStatus":400,"severity":"error","filePath":"apps/api/plane/utils/filters/filter_backend.py","lineNumber":73,"sourceCode":"    def _normalize_filter_data(self, raw_filter, source_label):\n        \"\"\"Return a dict from raw filter input or raise a ValidationError.\n\n        - raw_filter may be a dict or a JSON string\n        - source_label is used in error messages (e.g., 'filter_data' or 'filter')\n        \"\"\"\n        try:\n            if isinstance(raw_filter, str):\n                return json.loads(raw_filter)\n            if isinstance(raw_filter, dict):\n                return raw_filter\n            raise DRFValidationError(\n                {\n                    \"message\": f\"'{source_label}' must be a dict or a JSON string.\",\n                    \"code\": \"invalid_filter_type\",\n                }\n            )\n        except json.JSONDecodeError:\n            raise DRFValidationError(\n                {\n                    \"message\": (f\"Invalid JSON for '{source_label}'. Expected a valid JSON object.\"),\n                    \"code\": \"invalid_json\",\n                }\n            )\n\n    def _apply_json_filter(self, queryset, filter_data, view):\n        \"\"\"Process a JSON filter structure using Q object composition.\"\"\"\n        if not filter_data:\n            return queryset\n\n        # Validate structure and depth before field allowlist checks\n        max_depth = self._get_max_depth(view)\n        self._validate_structure(filter_data, max_depth=max_depth, current_depth=1)\n\n        # Validate against the view's FilterSet (only declared filters are allowed)\n        self._validate_fields(filter_data, view)\n","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/api/plane/utils/filters/filter_backend.py#L55-L91","documentation":"Raised by _normalize_filter_data when json.loads() throws JSONDecodeError on a string filter input. The query parameter `filters` (filter_param = 'filters') or the explicit filter_data string must decode to a valid JSON object; trailing commas, single quotes, unquoted keys, or truncated payloads all fail here.","triggerScenarios":"GET /api/workspaces/<slug>/issues/?filters=state%3Dopen ; GET .../?filters={state:open} (unquoted keys); GET .../?filters={\"state\":\"open\",} (trailing comma); URL truncation producing a partial JSON body.","commonSituations":"Hand-built query strings in tests or Postman; URL-encoding mistakes (forgetting to encode braces/quotes); clients that build the filter with string concatenation instead of JSON.stringify; copy-paste from a JSON view that omits outer braces.","solutions":["Build the param with JSON.stringify on the client: `?filters=${encodeURIComponent(JSON.stringify({state:'open'}))}`.","Validate the JSON locally before sending: parse it in your dev console to surface the exact syntax error.","Watch for trailing commas, single-quoted strings, and bare keys; standard JSON requires double quotes everywhere."],"exampleFix":"// before\nfetch(`/issues/?filters={state:open}`)\n// after\nfetch(`/issues/?filters=${encodeURIComponent(JSON.stringify({state:'open'}))}`)","handlingStrategy":"validation","validationCode":"import json\n\ndef safe_json_filter(raw: str):\n    try:\n        parsed = json.loads(raw)\n    except json.JSONDecodeError as e:\n        raise ValueError(f'filters param is not valid JSON: {e}') from e\n    if not isinstance(parsed, dict):\n        raise ValueError('filters param must decode to a JSON object')\n    return parsed","typeGuard":"// JS, before constructing the URL\nfunction buildFiltersParam(obj) {\n  if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {\n    throw new Error('filters must be a plain object');\n  }\n  return encodeURIComponent(JSON.stringify(obj));\n}","tryCatchPattern":"try:\n    normalized = backend._normalize_filter_data(raw, 'filters')\nexcept DRFValidationError as e:\n    if e.detail.get('code') == 'invalid_json':\n        return Response({'error': 'malformed filters param'}, status=400)\n    raise","preventionTips":["Always build the query string with JSON.stringify + encodeURIComponent on the client.","Run the filter through a local JSON.parse in the browser console before sending."],"tags":["filters","json","validation","api"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}