PaddlePaddle/PaddleOCR · error · ResultParseError
Malformed JSONL result payload: {e}
Error message
Malformed JSONL result payload: {e} What it means
ResultParseError from fetch_jsonl in the async API client: the poller downloads the job's result JSONL URL, splits it into lines, and json.loads each non-empty line; any line that is not valid JSON raises with the underlying JSONDecodeError chained. This indicates the downloaded result payload is not the expected line-delimited JSON.
Source
Thrown at paddleocr/_api_client/_async_http.py:163
return await self._response_data(resp)
async def get_batch_status(self, batch_id: str) -> Dict[str, Any]:
await self._ensure_session()
async with self._session.get(f"{self._jobs_url}/batch/{batch_id}") as resp:
await self._raise_for_response(resp)
return await self._response_data(resp)
async def fetch_jsonl(self, url: str) -> list:
timeout = aiohttp.ClientTimeout(total=self._timeout)
async with aiohttp.ClientSession(timeout=timeout) as bare_session:
async with bare_session.get(url) as resp:
await self._raise_for_response(resp)
text = await resp.text()
try:
lines = text.strip().split("\n")
return [json.loads(line) for line in lines if line.strip()]
except json.JSONDecodeError as e:
raise ResultParseError(
f"Malformed JSONL result payload: {e}"
) from e
async def _raise_for_response(self, resp) -> None:
if 200 <= resp.status < 300:
return
try:
body = await resp.json()
msg = (
extract_api_message_from_payload(body)
if isinstance(body, dict)
else None
)
if not msg:
msg = await resp.text()
except Exception:
msg = await resp.text()
raise_for_status(resp.status, msg)View on GitHub (pinned to 2661c7c0ef)
Solutions
- Fetch the URL manually (curl) and inspect the first lines — HTML/XML means an auth or expiry problem, not corruption.
- Retry the poll/parse once; transient truncation or proxy pages often clear.
- Upgrade the paddleocr API client if the service changed its result format.
- Ensure the result URL is fetched promptly after job completion before any expiry window closes.
Defensive patterns
Strategy: retry
Validate before calling
import json, urllib.request
def jsonl_url_fetchable(url: str) -> bool:
try:
with urllib.request.urlopen(url, timeout=30) as r:
first = r.readline().decode()
json.loads(first)
return True
except Exception:
return False Try / catch
from paddleocr._api_client.errors import ResultParseError
for attempt in range(2):
try:
data = await http.fetch_jsonl(url)
break
except ResultParseError:
if attempt == 1:
raise # persistent: URL likely returns an error page, not JSONL
await asyncio.sleep(2) Prevention
- Fetch result URLs promptly after job completion, before pre-signed links expire.
- On parse failure, curl the URL once to distinguish an HTML error page from real corruption.
- Pin the api-client version matching the service's result format.
When it happens
Trigger: The result URL returns an HTML error page (auth expiry, S3 error XML) with a 200 status; a partially downloaded/truncated file; the endpoint switching format (e.g. a single JSON object or CSV) so one line fails to parse.
Common situations: Pre-signed result URLs expiring between job completion and fetch; proxies injecting content; API version changes altering the result format.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- {file_path}
- Response body is not valid JSON: {e}
- Timed out after {elapsed:.1f}s waiting for job {job_id}
- Malformed JSONL result payload: {e}
- Failed to parse JSONL result payload.
AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14).
Data as JSON: /api/errors/c53540ad3f16b3e7.
Report an issue: GitHub.