How do I turn a PDF into JSON that a script can read?
Upload the PDF to PDF to JSON and you get one JSON file: the title, then the text grouped under each section heading, with tables as rows and columns. On 2026-10-13 we tried it on two files. A script could read both, but table values arrive as text. Heading levels did not follow the section numbers that day; we changed that, and the same file is now correct.
What did we convert?
We used two files. The first is a two-page report we made, with invented figures: a title, three numbered sections, a bullet list, a table, a figure and a long paragraph. The second is an 8-page excerpt of a real research paper from arXiv, which has an author block, formulas, three tables and three images. We uploaded both to PDF to JSON on osmanthdocs.com and read the files that came back. The report was labelled "Looks good". The paper was labelled "Review recommended", because two of its formulas were flagged.
What is in the file?
The top level has nine keys: title, pages, pdf_type, quality, notes, sections, tables, images and formulas. Most of the content sits under sections. Each section has a heading, a level, a page and a content list. Each item in the list has a type. We saw paragraph, list_item, table, image, formula and footnote.
| In the PDF | In the JSON |
|---|---|
| Title and three numbered section headings | Four sections, in page order, titled as in the PDF |
| Bullet list under "1. Summary" | Three list_item entries inside that section |
| Table with 4 columns and 4 data rows | One table item with header and rows, also copied into the top-level tables list |
| Figure | An image item with a file name such as image_001.png, and a caption of null |
| The figure caption | A separate paragraph right after the image |
| Text before the first heading in the paper | A first section with heading set to null and level set to 0 |
The file is UTF-8 text with no byte order mark, which is what RFC 8259 asks of JSON exchanged between systems.
How do I read it in a script?
This Python script reads the table from the report and turns the cells into numbers. We ran it on the file from the site, and it printed four lines, one per region.
import json
with open("report.json", encoding="utf-8") as f:
doc = json.load(f)
for section in doc["sections"]:
for item in section["content"]:
if item["type"] == "table":
for row in item["rows"]:
region = row[0]
shipments = int(row[1].replace(",", ""))
on_time = float(row[2].rstrip("%"))
print(region, shipments, on_time)
The two conversion lines are needed. In the file, the table values are strings such as "1,240" and "96.5%", not numbers, so a script has to clean them first. The Python documentation says json.load takes a file object and returns Python objects, and that it keeps the order of the keys by default. We pass encoding="utf-8" to open so that the file reads the same on every machine.
What surprised us?
- Heading levels were flat, and are now fixed for numbered headings. On 2026-10-13 the first run on the paper returned 23 headings that were all level 2, including "3.1 Encoder and Decoder Stacks" and "3.2.1 Scaled Dot-Product Attention". We changed the tool the same day so that a heading starting with a section number gets the level its number gives: "3" is level 2, "3.1" level 3 and "3.2.1" level 4. We ran the same file again on the live site on 2026-10-14. The result has 9 headings at level 2, 11 at level 3 and 3 at level 4. A heading with no number is still level 2, so an unnumbered subheading can sit at the wrong depth.
- An author block became a table. In the paper, the four-column block of author names and email addresses was returned as a table under the title heading. The
tableslist therefore holds more than data tables. - Table cells hold text. In the paper, a cell such as
O ( n 2 · d )is plain text with spaces between the symbols. - The title may not be level 1. In the paper, a permission notice came before the title, so the title was not promoted to level 1. It was level 2, both before and after the change.
- A figure label can look like a heading. The paper has a heading "Scaled Dot-Product Attention" that is followed by two images and then a paragraph. It appears to be a label inside a figure, not a section title. We did not compare it with the page image.
What else does the job return?
The job result lists other files. One is a more detailed structured file that gives every block a page number and a position on the page. We looked at it for the report. It is useful if your script needs to know where on the page something sat. The Docling documentation, for the open-source engine behind the tool, describes its own document model with separate lists of texts, tables and pictures and a tree that gives the reading order. The simpler file you get from the tool is not that model, so do not expect the same field names.
What are the limits?
This is two files, so it shows how the output looks and not how often it is right. Check the numbers in a table against the PDF before you rely on them, and treat a "Review recommended" label as a reason to look. We did not test scanned PDFs here; see how to extract text from a scanned PDF. If you want the same content for an AI assistant rather than a script, see how to give a PDF to ChatGPT or Claude.
Your PDF is uploaded to our servers, processed with open-source software, and deleted when processing finishes. The results stay for 60 minutes.
Sources
- RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format (published December 2017, checked 2026-10-13)
- Python documentation: json, JSON encoder and decoder (page documents Python 3.15.0, checked 2026-10-13)
- Docling documentation: DoclingDocument concept (no date shown, checked 2026-10-13)