{"record":{"id":"6e8e2cc7b28d6404","repo":"NanmiCoder/MediaCrawler","slug":"invalid-json-file","errorCode":null,"errorMessage":"Invalid JSON file","messagePattern":"Invalid JSON file","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"api/routers/data.py","lineNumber":154,"sourceCode":"                    return {\"data\": rows, \"total\": total}\n            elif full_path.suffix.lower() in (\".xlsx\", \".xls\"):\n                import pandas as pd\n                # Read first limit rows\n                df = pd.read_excel(full_path, nrows=limit)\n                # Get total row count (only read first column to save memory)\n                df_count = pd.read_excel(full_path, usecols=[0])\n                total = len(df_count)\n                # Convert to list of dictionaries, handle NaN values\n                rows = df.where(pd.notnull(df), None).to_dict(orient='records')\n                return {\n                    \"data\": rows,\n                    \"total\": total,\n                    \"columns\": list(df.columns)\n                }\n            else:\n                raise HTTPException(status_code=400, detail=\"Unsupported file type for preview\")\n        except json.JSONDecodeError:\n            raise HTTPException(status_code=400, detail=\"Invalid JSON file\")\n        except Exception as e:\n            raise HTTPException(status_code=500, detail=str(e))\n    else:\n        # Return file download\n        return FileResponse(\n            path=full_path,\n            filename=full_path.name,\n            media_type=\"application/octet-stream\"\n        )\n\n\n@router.get(\"/download/{file_path:path}\")\nasync def download_file(file_path: str):\n    \"\"\"Download file\"\"\"\n    full_path = DATA_DIR / file_path\n\n    if not full_path.exists():\n        raise HTTPException(status_code=404, detail=\"File not found\")","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/NanmiCoder/MediaCrawler/blob/d6f7c5bb906b6dac40ddf343ef9e26438a3de092/api/routers/data.py#L136-L172","documentation":"HTTP 400 raised in the preview branch when json.load() raises JSONDecodeError for a .json-suffixed file. The file exists and passed the security check, but its content is not valid JSON (truncated, empty, BOM-corrupted, or NDJSON).","triggerScenarios":"A .json file still being written by the crawler when the preview request lands (truncated tail); an empty .json file from a crashed run; JSON Lines output saved with a .json extension, which json.load rejects because it expects a single document.","commonSituations":"Previewing data while a crawl is actively writing; file transfer that truncated the JSON; BOM from a Windows editor breaking the first token.","solutions":["Wait for the crawler to finish (GET /crawler/status) before previewing output files.","Open the file directly and inspect it — json.tool or a validator will point at the exact offending position.","If the pipeline writes JSON Lines, rename to .jsonl and add a line-by-line reader, or emit proper JSON arrays.","Re-run or regenerate the file if it is an empty/truncated artifact of a failed run."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const st = await (await fetch('/api/crawler/status')).json();\nif (!st.running) { /* safe to preview: files are fully written */ }","typeGuard":null,"tryCatchPattern":"try { await fetch(`/api/data/files/${path}?preview=true`); } catch (e) { if (e.status === 400 && e.detail === 'Invalid JSON file') { /* fetch raw and inspect, or retry after crawl ends */ } }","preventionTips":["Don't preview files while the crawler is writing them","Write JSON atomically (temp file + rename) in producer code","Use .jsonl extension for line-delimited JSON so it never hits json.load"],"tags":["api","http-400","json","data-files","parse-error"],"backgroundTag":null,"analyzedSha":"d6f7c5bb906b6dac40ddf343ef9e26438a3de092","analyzedAt":"2026-08-15T01:39:07.505Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}