How OCR Can Output Structured JSON or XML
← All posts
TutorialSep 7, 2026· 8 min read

How OCR Can Output Structured JSON or XML

OCR used to mean one thing: pixels in, messy text out. If you searched for how OCR can output structured JSON or XML, you are past that stage. You want named fields (amounts, dates, IDs, titles) in a format your database, agent, or integration can consume without another brittle parser.

Short answer: modern OCR APIs can return structured JSON directly when you name the fields you need. Native XML responses are rarer. When a legacy system still expects XML, the durable pattern is JSON first, then a deterministic JSON-to-XML transform in your own code. That keeps extraction typed and predictable while still feeding SOAP endpoints, EDI adapters, or older document stores.

What “structured” means for OCR

Unstructured OCR is a Markdown or plain-text dump of whatever the model read. Useful for search, RAG chunking, or human review. Not enough when you need invoice_date as YYYY-MM-DD and total_amount as a number.

Structured OCR output is a record: keys you choose, values with consistent types. In OCRskill, that is the POST /ocr.json endpoint. You pass a comma-separated fields list; the response is typed JSON built from those names. Required fields fail loudly with 400 if missing. Optional fields (append ? to the name) are omitted when absent. Dates normalize to ISO YYYY-MM-DD. Strings are trimmed.

That is a different product from Google/schema.org “structured data” (JSON-LD for web pages). Here the structure is the extraction result, not markup you publish for crawlers.

JSON vs XML: which should OCR return?

Both formats can carry the same fields. The tradeoffs are about ecosystems, not OCR accuracy.

Concern JSON XML
Agent and web APIs Default for most REST and OpenAI-style tools Still common in enterprise SOAP and some government schemas
Typing and tooling First-class in JS/TS, Python, Go; easy schema validation Strong with XSD; heavier client libraries
Verbosity Compact More tags and namespaces
Schema evolution Add optional keys carefully XSD versioning and namespaces
Nested documents Objects and arrays Elements, attributes, mixed content

For new pipelines, prefer JSON from the OCR step. OCRskill returns JSON from /ocr.json and Markdown from /ocr. There is no separate /ocr.xml today. If your consumer only speaks XML, convert after extraction. Do not ask the vision model to invent XML tags in free text; that recreates the fragile parsing problem structured OCR was built to remove.

Path 1: Image or document to structured JSON

Get a key, name fields, upload a file:

curl https://api.ocrskill.com/get-key.json

curl "https://api.ocrskill.com/ocr.json?fields=company_name,invoice_date,total_amount,seller_name?" \
  -H "Authorization: Bearer sk-your-key-here" \
  -F "file=@invoice.pdf"

Example response shape:

{
  "company_name": "ACME Supplies SRL",
  "invoice_date": "2026-08-14",
  "total_amount": 1280.5
}

seller_name was optional and missing, so it does not appear. Supported uploads include common images (PNG, JPEG, WebP, GIF, BMP, TIFF) and documents (PDF, Word, Excel, PowerPoint, OpenDocument, RTF, CSV). Full field lists and error cases live in the Structured Data Extraction API reference.

When you are unsure which fields exist, omit fields for discovery mode: the API treats supported fields as optional and returns only what it finds. Lock the schema once you know the document type.

For a product-level walkthrough of /ocr.json, see From Pixels to Typed JSON. For plain Markdown instead of fields, use the simplified /ocr upload.

Path 2: Structured JSON to XML for legacy consumers

Once you have a stable JSON object, XML is a serialization problem, not an OCR problem. Keep the mapping explicit so tags stay consistent across vendors and layouts.

Python example with the standard library:

import json
import xml.etree.ElementTree as ET
from pathlib import Path

def json_record_to_xml(record: dict, root_tag: str = "Document") -> str:
    root = ET.Element(root_tag)
    for key, value in record.items():
        child = ET.SubElement(root, key)
        child.text = "" if value is None else str(value)
    return ET.tostring(root, encoding="unicode")

record = json.loads(Path("invoice.json").read_text())
xml_body = json_record_to_xml(record, root_tag="Invoice")
print(xml_body)

Possible output:

<Invoice>
  <company_name>ACME Supplies SRL</company_name>
  <invoice_date>2026-08-14</invoice_date>
  <total_amount>1280.5</total_amount>
</Invoice>

For production, prefer a schema-aware library (or an XSD-driven mapper) so element order, namespaces, and numeric formatting match the downstream contract. The important part is that OCR already committed to typed fields; the XML step should not re-interpret free text.

Node.js sketch with a small helper:

function jsonToXml(obj, root = "Document") {
  const escape = (s) =>
    String(s)
      .replaceAll("&", "&amp;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;")
      .replaceAll('"', "&quot;");
  const body = Object.entries(obj)
    .map(([k, v]) => `<${k}>${escape(v ?? "")}</${k}>`)
    .join("");
  return `<?xml version="1.0" encoding="UTF-8"?><${root}>${body}</${root}>`;
}

If you must emit attributes, CDATA, or deeply nested line items, define that mapping in code against the JSON you already validated. Do not prompt an LLM to “make this XML” unless you validate the result against an XSD every time.

Path 3: Markdown OCR, then structure yourself

Sometimes you need the full reading order (tables, headings, footnotes) before you know the schema. Call /ocr for Markdown, then structure in a second step with your own schema or an LLM structured-output call. That two-step flow is slower and costs more tokens, but it helps for novel layouts. Once the schema stabilizes, move those fields onto /ocr.json so extraction stays one request.

Design the schema before you scale

A few rules that keep JSON and XML consumers honest:

  1. Name fields after the business concept, not the visual label (invoice_date, not date_top_right).
  2. Mark uncertain fields optional until your document set proves they always appear.
  3. Normalize in the OCR layer (ISO dates, trimmed strings) so JSON and XML both inherit the same values.
  4. Version the contract (Invoice vs InvoiceV2, or a schema_version field) when you add required keys.
  5. Fail closed on required data. A loud 400 beats silent nulls in accounting or KYC.

Limitations to plan around

  • OCRskill’s structured endpoint returns JSON, not XML. XML is your post-processing step.
  • Field names must match the supported list. Unknown or duplicate names return 400.
  • Extraction quality still depends on image quality, language coverage, and whether the value is actually on the page.
  • Nested line-item arrays and arbitrary custom schemas beyond the published fields are not a free-form JSON Schema upload on /ocr.json today. Stick to documented fields, or OCR to Markdown and structure elsewhere.

Conclusion

OCR can output structured JSON today by naming fields on a dedicated extraction endpoint. XML remains a valid delivery format for older systems, but it should sit one step after typed JSON, not inside the vision model. Start with /ocr.json for the records you need, keep Markdown /ocr for exploratory reading order, and add a small, tested JSON-to-XML mapper only where a consumer still requires tags. That split keeps search intent, agent tooling, and legacy integrations aligned without rebuilding parsers for every new layout.

Grab a free API key, extract one document to JSON, and only then decide whether anyone still needs the XML wrapper.