{"record":{"id":"f0d199c063add28a","repo":"infiniflow/ragflow","slug":"json-payload-must-be-an-object","errorCode":null,"errorMessage":"JSON payload must be an object.","messagePattern":"JSON payload must be an object\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"warning","filePath":"api/utils/api_utils.py","lineNumber":81,"sourceCode":"    if hasattr(request, \"_cached_payload\"):\n        return request._cached_payload\n    payload: Any = None\n\n    body_bytes = await request.get_data()\n    has_body = bool(body_bytes)\n    content_type = (request.content_type or \"\").lower()\n    is_json = content_type.startswith(\"application/json\")\n\n    if not has_body:\n        payload = {}\n    elif is_json:\n        payload = await request.get_json(force=False, silent=False)\n        if isinstance(payload, dict):\n            payload = payload or {}\n        elif isinstance(payload, str):\n            raise AttributeError(\"'str' object has no attribute 'get'\")\n        else:\n            raise TypeError(\"JSON payload must be an object.\")\n    else:\n        form = await request.form\n        payload = form.to_dict() if form else None\n        if payload is None:\n            raise TypeError(\"Request body is not a valid form payload.\")\n\n    request._cached_payload = payload\n    return payload\n\n\nasync def get_request_json():\n    return await _coerce_request_data()\n\n\ndef serialize_for_json(obj):\n    \"\"\"\n    Recursively serialize objects to make them JSON serializable.\n    Handles ModelMetaclass and other non-serializable objects.","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/api/utils/api_utils.py#L63-L99","documentation":"Raised by _coerce_request_data when a JSON body parses successfully but is not an object — arrays ([1,2]), numbers, booleans, or null. Handlers use .get() on the payload, so non-object JSON is rejected with TypeError at the parsing layer.","triggerScenarios":"POSTing a JSON array as the request body ('[\"a\",\"b\"]'), a bare number/true/null body, or an API client whose serializer emits a list where the endpoint expects an object like {\"ids\": [...]}.","commonSituations":"Clients wrapping batch payloads as bare arrays when the endpoint wants an object; copy-pasted example bodies from other APIs; null bodies sent explicitly.","solutions":["Wrap list payloads in an object: send {\"ids\": [...]} instead of [...].","Check the endpoint's documented request schema and match the top-level type.","Add a client-side assertion that the serialized body starts with '{' for JSON endpoints."],"exampleFix":"# before\nrequests.post(url, json=['doc1', 'doc2'])  # array -> TypeError\n\n# after\nrequests.post(url, json={'ids': ['doc1', 'doc2']})","handlingStrategy":"type-guard","validationCode":"assert isinstance(payload, dict), 'top-level JSON must be an object'","typeGuard":"def is_json_object(payload) -> bool:\n    return isinstance(payload, dict)","tryCatchPattern":"try:\n    data = await get_request_json()\nexcept TypeError:\n    return json_error_response('JSON payload must be an object', 400)","preventionTips":["Wrap arrays inside objects ({\"ids\": [...]}) per the endpoint schema.","Validate the top-level JSON type in client interceptors before sending."],"tags":["validation","json","http","request-body","type-error"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}