The Exact Prompts I Used to Force Claude into Outputting Error-Free JSON Product Data
"Output JSON" is the most dangerous two words in AI data extraction. Every model will happily comply — by wrapping your JSON in markdown fences, prefacing it with polite conversation, or hallucinating fields you did not ask for. After extracting 15,000+ product records from a 500-page PDF catalog, I arrived at 5 prompt rules that stopped the crashes. Here they are, with the failures that produced each one.
The Prompt Everyone Starts With (And Why It Fails)
Here is what I wrote on day one:
Extract the product data from this catalog page and output it as JSON.
DeepSeek returned:
Sure! Here is the extracted product data from the catalog page:
```json
[{"part_number": "16400-0T020", ...}]
```
Let me know if you need any adjustments!
My Python script called json.loads() on that response and immediately crashed. The model did exactly what I asked — it output JSON. But it also output conversation. And markdown fences. And a cheerful offer to help further.
This is not a bug. It is how chat models are trained. They are designed to be helpful, conversational assistants — not JSON-emitting machines. Every friendly word they add is a parsing error waiting to happen.
The fix is not to write a smarter parser. It is to write a prompt that never produces the bad output in the first place.
Rule 1: Ban Everything That Is Not JSON
Bad prompt: "Output JSON."
Good prompt:
Return ONLY valid JSON. No markdown. No code fences. No explanation.
No "here is the output." No "let me know if you need anything."
Start your response with { and end with }.
The last line is the cheat code. By instructing the model to start with {, you make it syntactically impossible to include a conversational prefix. The model cannot write "Sure! {" because that would be invalid text before valid JSON. This one constraint eliminated about 80% of my parsing errors.
I still added a cleanup step in Python — strip everything before the first { and after the last } — as insurance. But after adding this rule, the cleanup code almost never triggered.
Rule 2: Provide the Exact JSON Schema Inline
Bad prompt: "Each product should have a part number, description, dimensions, and fitment."
Good prompt:
Return ONLY valid JSON in this exact format:
{
"products": [
{
"part_number": "string or null",
"description": "string or null",
"dimensions": "string or null",
"fitment": "string or null"
}
]
}
Rules:
- Every field must be present in every object, even if null.
- Do not add extra fields. Do not rename fields.
- Do not nest objects or change the structure.
Without the inline schema, the model invents field names. One page would use "oem_code", the next "part_no", the next "reference". Your CSV ends up with 15 column variants for the same concept, and you spend an hour normalizing them by hand.
With the schema, the model knows exactly which keys to use and that null is acceptable. The "Do not add extra fields" line matters more than you would think — without it, some models add "confidence_score" or "extraction_notes" fields unprompted.
Rule 3: Explicitly Forbid Guessing
Bad prompt: (implicit) "Extract the data."
Good prompt:
If any field is unreadable, blurry, or ambiguous, use null for that field.
Do NOT guess. Do NOT infer. Do NOT use context to fill in missing values.
A null is better than a plausible-but-wrong value.
This is the rule that saved my database from silent corruption.
On one page, a part number was slightly blurred. The model guessed "16400-0T050" based on surrounding context. The real number was "16400-0T020" — a completely different radiator for a different car. I only caught it because I recognized the correct code from memory.
Models want to be helpful. When a cell is hard to read, they will use context clues to infer the likely value. This is excellent behavior for a chatbot. It is catastrophic for a data extraction pipeline. A null in your CSV takes 3 seconds to look up manually. A plausible-but-wrong part number could sit in your database for months before anyone notices — and by then, you have shipped the wrong parts to a customer.
After adding this rule, my null rate went from about 2% to about 8%. That sounds worse. It is not. I would rather fix 8 nulls I can see than ship 2 errors I cannot.
Rule 4: Tell the Model What to Ignore
Bad prompt: (nothing about page structure)
Good prompt:
Ignore: page headers, page footers, page numbers, watermarks,
company logos, section titles, and any text outside product blocks.
Only extract data from actual product entries.
Without this, the model treats the page number and the supplier's company name in the header as extractable data. I got CSV rows like:
| part_number | description |
|---|---|
| Page 42 | ABC Radiator Co., Ltd. |
Took me 10 minutes to figure out why my product count was higher than expected and why "Page 42" was listed as a radiator. The model was not wrong — there was text on the page, and I told it to extract text. I just did not tell it which text was noise.
Every document type has its own noise. For catalogs: headers, footers, page numbers, watermarks. For invoices: bank details, terms and conditions, legal fine print. For emails: signatures, disclaimers, forwarding headers. Tell the model explicitly what to skip.
Rule 5: Give Permission to Return Nothing
Bad prompt: (implicit expectation that every page has products)
Good prompt:
If the page contains no product data (section divider, blank page, index page,
table of contents), return: {"products": []}
Do not hallucinate products to fill an empty page.
My PDF had section dividers — full-page titles like "RADIATORS — TOYOTA APPLICATIONS" with no actual product data. The model, told to extract products from every page, sometimes invented one or two generic products to satisfy the instruction. Giving it explicit permission to return an empty array fixed this.
This pattern applies beyond catalogs. Whatever you are extracting — invoices, emails, support tickets — some inputs will contain zero instances of what you are looking for. Tell the model that zero is a valid answer.
The Final System Prompt
All five rules combined into the template I now use for every extraction task:
You are a data extraction tool. You do not chat. You do not explain.
You output ONLY valid JSON.
Return data in this exact format:
{
"products": [
{
"part_number": "string or null",
"description": "string or null",
"dimensions": "string or null",
"fitment": "string or null"
}
]
}
Rules:
1. Start your response with { and end with }.
2. No markdown. No code fences. No explanation. No conversation.
3. Every field must be present in every object, even if null.
4. Do not add extra fields. Do not rename fields.
5. If a field is unreadable or ambiguous, use null. Do NOT guess.
6. Ignore page headers, footers, page numbers, watermarks, and logos.
7. If the page contains no products, return {"products": []}.
8. Preserve exact codes — dashes, slashes, and punctuation matter.
This prompt is not clever. It is not optimized for token count. It is optimized for one thing: never crashing my Python script. I have run it against 500+ pages and it has not produced a JSON parse error once. When a page fails, it fails cleanly — an empty array or a null field that I can fix manually.
That is the real goal. Not perfect extraction. Predictable extraction.
The Python Safety Net (For When the Prompt Still Fails)
Prompts are not 100% reliable. Network issues, model updates, and edge-case pages will occasionally produce unparseable output. You need a second layer:
import json
def safe_parse_json(raw_response):
"""Try to parse JSON from a model response. If it fails, return None."""
content = raw_response.strip()
# Strip conversation: find the JSON object
if "{" in content:
content = content[content.index("{"):]
if "}" in content:
content = content[:content.rindex("}") + 1]
# Remove common wrappers
content = content.replace("```json", "").replace("```", "").strip()
try:
return json.loads(content)
except json.JSONDecodeError:
return None
# Usage in the processing loop:
result = safe_parse_json(raw_response)
if result is None:
# Save the raw response for manual review instead of crashing
with open(f"failed_{filename}.txt", "w") as f:
f.write(raw_response)
print(f" Warning: {filename} failed to parse — saved for review")
continue
products = result.get("products", [])
This function does three things: strips conversational wrappers, removes markdown code fences, and returns None instead of throwing an exception. When a parse fails, the script logs it and moves to the next page instead of dying. You review the failed pages manually afterward.
The prompt prevents 95% of failures. This function catches the other 5%. Together, they mean your script runs from page 1 to page 500 without human intervention — which matters when you are processing overnight or while making tea.
What I Still Get Wrong
Even with these rules, there are pages where extraction fails:
- Radically different layouts within the same document. If page 47 suddenly switches from 3-column to 2-column with a different field arrangement, the schema no longer matches. You need to detect the format change and route those pages to a different prompt.
- Handwritten annotations. If someone scribbled notes on a scanned page, the model sometimes incorporates the handwriting into the product data. I have not found a prompt-based fix for this. The solution is manual review of pages with visible handwriting.
- Mixed languages in the same cell. Chinese annotations next to English part numbers sometimes get merged into one string. Telling the model to "extract only the English part number" helps but does not entirely fix it.
No prompt is perfect. The goal is to make failures predictable — you know which kinds of pages will break, and you have a process for handling them — rather than having the script randomly crash on page 147 at 2 AM.
Related Articles
- The $1 Tech Stack: How I Connected Claude and DeepSeek APIs for Massive Data Extraction — the full setup, the working script, and the five crashes that shaped this prompt.
- Why Standard OCR Fails on 500-page PDF Catalogs (And How I Solved It with AI Vision) — why AI vision beats OCR on multi-column B2B catalogs.
- How I Built a 15,000+ Product Database for Under $1 Using Claude and DeepSeek — the full database pipeline.
→ Back to AI Export Lab — all build notes from this experiment.