{"record":{"id":"1fd482b35a934257","repo":"infiniflow/ragflow","slug":"bigquery-service-account-json-must-be-a-json-stri","errorCode":null,"errorMessage":"BigQuery: service_account_json must be a JSON string or object","messagePattern":"BigQuery: service_account_json must be a JSON string or object","errorType":"validation","errorClass":"ConnectorMissingCredentialError","httpStatus":null,"severity":"error","filePath":"common/data_source/bigquery_connector.py","lineNumber":163,"sourceCode":"        \"\"\"Load BigQuery service-account credentials.\n\n        Accepts ``service_account_json`` as either a dict or a JSON string.\n        \"\"\"\n        logging.debug(\"Loading credentials for BigQuery project: %s\", self.project_id)\n\n        raw = (credentials or {}).get(\"service_account_json\")\n        if not raw:\n            raise ConnectorMissingCredentialError(\"BigQuery: missing service_account_json\")\n\n        if isinstance(raw, str):\n            try:\n                service_account_info = json.loads(raw)\n            except json.JSONDecodeError as exc:\n                raise ConnectorMissingCredentialError(f\"BigQuery: service_account_json is not valid JSON: {exc}\")\n        elif isinstance(raw, dict):\n            service_account_info = raw\n        else:\n            raise ConnectorMissingCredentialError(\"BigQuery: service_account_json must be a JSON string or object\")\n\n        self._credentials = {\"service_account_info\": service_account_info}\n        return None\n\n    def _get_client(self):\n        \"\"\"Create and cache a BigQuery client from the loaded service account.\"\"\"\n        if self._client is not None:\n            return self._client\n\n        if bigquery is None or service_account is None:\n            raise ConnectorValidationError(\"BigQuery client not installed. Please install google-cloud-bigquery.\")\n\n        service_account_info = self._credentials.get(\"service_account_info\")\n        if not service_account_info:\n            raise ConnectorMissingCredentialError(\"BigQuery credentials not loaded.\")\n\n        try:\n            creds = service_account.Credentials.from_service_account_info(service_account_info)","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/common/data_source/bigquery_connector.py#L145-L181","documentation":"Type-validation branch in load_credentials: service_account_json exists but is neither a str nor a dict (e.g. a list, int, or nested structure). The connector accepts exactly two shapes — JSON string or mapping — and rejects anything else as a credential error.","triggerScenarios":"Passing service_account_json as a list (e.g. [sa_dict] accidentally wrapped), an int/bool from a misconfigured form, or bytes (an encoded JSON key file read as b'...') — bytes are not decoded, so they fail this branch.","commonSituations":"Glue code that wraps the value in a list 'just in case', reading the key file in binary mode and passing bytes, or a config schema that auto-parses JSON into a list structure.","solutions":["Pass the parsed dict, the JSON string, or decode bytes first: raw.decode('utf-8')","Remove accidental list wrapping: use sa_json, not [sa_json]","Add a pre-flight isinstance check in the config loader so the wrong shape is caught at config time"],"exampleFix":"# before\ncreds = {\"service_account_json\": [sa_dict]}\n# or\ncreds = {\"service_account_json\": key_file_bytes}\n\n# after\ncreds = {\"service_account_json\": sa_dict}\n# or\ncreds = {\"service_account_json\": key_file_bytes.decode(\"utf-8\")}","handlingStrategy":"type-guard","validationCode":"def normalize_sa_input(raw):\n    if isinstance(raw, bytes):\n        raw = raw.decode(\"utf-8\")\n    if isinstance(raw, list) and len(raw) == 1:\n        raw = raw[0]  # unwrap accidental list\n    if not isinstance(raw, (str, dict)):\n        raise TypeError(f\"service_account_json must be str|dict, got {type(raw).__name__}\")\n    return raw","typeGuard":"def is_sa_json_shape(raw) -> bool:\n    return isinstance(raw, (str, dict))","tryCatchPattern":"try:\n    connector.load_credentials(creds)\nexcept ConnectorMissingCredentialError as e:\n    if \"must be a JSON string or object\" in str(e):\n        creds[\"service_account_json\"] = normalize_sa_input(creds[\"service_account_json\"])\n        connector.load_credentials(creds)\n    else:\n        raise","preventionTips":["Decode bytes and unwrap single-element lists at the config boundary","Type credential fields in your config layer (pydantic/mypy) so wrong shapes fail at load time","Add unit tests asserting both accepted shapes (str and dict) and rejection of list/bytes"],"tags":["bigquery","gcp","type-validation","credentials","configuration"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}