How to Convert a PDF Table Into a JSON Array

Convert a PDF table into a JSON array. Define a schema, preserve data types, process scanned PDFs with OCR, and validate the result.

PdfParse Team

A PDF table is made for reading. Your application wants something else entirely: one JSON object per row, with stable keys and real data types instead of text that happens to line up in columns.

This guide converts a small invoice table into a JSON array. The order of operations matters: decide on the schema first, extract second, and validate before you trust any of it.

What good output looks like

Here's the source table — two invoice records:

InvoiceDateVendorPaidAmount
INV-104230 Jun 2026Northwind Office SupplyYes$297.00
INV-104807 Jul 2026Contoso ShippingNo$84.50

And here's the JSON we're aiming for — one object per row:

[
  {
    "invoice_number": "INV-1042",
    "invoice_date": "2026-06-30",
    "vendor": "Northwind Office Supply",
    "paid": true,
    "amount": 297.0,
    "currency": "USD"
  },
  {
    "invoice_number": "INV-1048",
    "invoice_date": "2026-07-07",
    "vendor": "Contoso Shipping",
    "paid": false,
    "amount": 84.5,
    "currency": "USD"
  }
]

Every object is a row. Every key means one thing, always. If your output doesn't have those two properties, nothing downstream will either.

1. Decide what one object represents

Before anything touches a converter, answer one question: what is a single JSON object?

Here, one object is one invoice row. The trap to avoid is mixing table-level facts into row-level objects without a structure — a report title is not a property of row three.

If the PDF has a report title above the table, give those values their own place:

{
  "report_name": "Open invoices",
  "report_date": "2026-07-10",
  "records": [
    {
      "invoice_number": "INV-1042",
      "amount": 297.0
    }
  ]
}

Use this shape when the report values apply to every row. Otherwise, keep the array flat.

2. Define the schema

One key per concept, one type per key, no surprises:

{
  "invoice_number": "string",
  "invoice_date": "date",
  "vendor": "string",
  "paid": "boolean",
  "amount": "number",
  "currency": "string"
}

A few rules worth being boring about:

  • Use snake_case for all keys.
  • Use one key for each concept.
  • Store numbers as numbers.
  • Store true or false values as booleans.
  • Store dates in YYYY-MM-DD format.
  • Store missing values as null when the schema permits it.

Keep the currency symbol out of amount — the $ is presentation, not data. The currency code gets its own field:

Incorrect:

{ "amount": "$297.00" }

Correct:

{ "amount": 297.0, "currency": "USD" }

3. Check whether the PDF is digital or scanned

Quick test: try to select a single word in the source table.

If the word highlights, you're usually dealing with a digital PDF. If the whole page selects as one slab, it's an image wearing a PDF costume, and OCR has to read it before anything can extract it.

With scanned pages, give punctuation and small characters a second look. OCR can shift a decimal point, swap a date separator, or "fix" a letter in an invoice code. Whatever it produces, don't feed OCR output downstream without a value check.

4. Convert the table

Open the PDF to JSON converter and run through it:

  1. Upload the PDF.
  2. Define the JSON fields.
  3. Set the type for each field.
  4. Mark the table rows as repeating records.
  5. Start the extraction.
  6. Compare the output with the source table.
  7. Download the JSON file.

Start with your most awkward PDF, not the cleanest one. Fix the schema on the hard file, and the rest of the batch gets much easier.

5. Keep wrapped cells in one piece

A PDF will happily wrap one cell across two visual lines. It's still one value:

Source:

Northwind Office
Supply

Output:

{ "vendor": "Northwind Office Supply" }

One row is one row, even when it takes two lines to say so.

Multi-page tables like to repeat their header on every page. Those repeated headers don't belong in your array — and neither do page numbers, report titles, or subtotal labels, unless your schema explicitly asks for them.

6. Nest only when the data is actually nested

Some rows contain their own repeating data — an invoice with line items, for example. That's what nested arrays are for:

{
  "invoice_number": "INV-1042",
  "vendor": "Northwind Office Supply",
  "items": [
    {
      "description": "Ergonomic keyboard",
      "quantity": 1,
      "unit_price": 95.0
    },
    {
      "description": "Monitor arm",
      "quantity": 3,
      "unit_price": 60.0
    }
  ]
}

Don't flatten line items into one comma-separated string. It looks tidy and quietly destroys the record boundaries and types you just worked to create.

If you need to query, join, or aggregate line items across many documents, a child table beats a nested array. The PDF to SQLite converter is built for exactly that workflow.

7. Validate the JSON

Step one: prove the file actually parses.

import { readFile } from "node:fs/promises";

const text = await readFile("invoices.json", "utf8");
const records = JSON.parse(text);

if (!Array.isArray(records)) {
  throw new Error("The root value must be an array.");
}

Step two: check every record.

for (const [index, record] of records.entries()) {
  if (typeof record.invoice_number !== "string") {
    throw new Error(`Record ${index}: invoice_number must be a string.`);
  }

  if (typeof record.amount !== "number") {
    throw new Error(`Record ${index}: amount must be a number.`);
  }

  if (typeof record.paid !== "boolean") {
    throw new Error(`Record ${index}: paid must be a boolean.`);
  }
}

Then go back to the source PDF and compare:

  • Count the source rows.
  • Count the JSON objects.
  • Check the first and last record.
  • Check the largest and smallest number.
  • Check blank values.
  • Check wrapped cells.
  • Check records at each page break.

A successful JSON.parse only proves the file is valid JSON. It says nothing about whether the values are right — that part is on you.

8. Know the limits

Some inputs deserve a human look no matter how good the tooling:

  • low-resolution scans
  • handwriting
  • rotated pages
  • merged cells
  • tables without clear columns
  • nested tables
  • password-protected PDFs
  • values that use regional number formats

That last one bites quietly: 1,234.56 and 1.234,56 are the same amount written for different regions, and parsing with the wrong convention changes the value. Decide on the expected format before anything becomes a JSON number.

JSON earns its keep

JSON is the right output when the destination is an API, an automation, or application code. If the data is flat and headed for a spreadsheet, use CSV. If you need related tables and SQL queries, use SQLite.

Still choosing the handoff? The CSV vs JSON vs SQLite comparison includes one source PDF, downloadable outputs, and validation commands for all three formats.

Ready when you are: open the PDF to JSON converter. You can also review the PDF document parser, the API documentation, and pricing.