Skip to content
trending

Guides

Model Promised JSON, Parser Returned Null: Five Runs, Three Causes

You ask a model for JSON, it promises JSON, and your parser returns null. Here's why it happens and what actually works when dealing with long-form content.

By 5 min read
Model Promised JSON, Parser Returned Null: Five Runs, Three Causes

👁 114 reads

You ask a model for JSON, the model promises JSON, and `json_decode` returns `null`. I ran into this problem this week while building an automation that writes articles - and in the process, I understood why an entire category of new tools is emerging around this exact issue.

Bottom Line

A text-generating model can never guarantee structure - it can only promise it. JSON mode and structured outputs dramatically improve reliability, but they still force format onto a system that fundamentally produces tokens sequentially. For short responses, that’s enough. For long ones - where a field contains an entire HTML article - things start to break. Three things that actually help: a tolerant parser, shorter fields, and avoiding one giant object.

What Actually Breaks

My case: an automation requests an object with four fields, where the last one contains a 1,200-word article in HTML. The model returned a response that started correctly - in the logs I could see a clean JSON opening with title and description. But the parser returned nothing.

There are three causes, and they occur together:

  1. Truncation at token limit. A long article doesn’t fit within the limit, the response ends mid-string, the JSON has no closing. The content is complete, the structure isn’t.
  2. Raw newline characters in strings. In valid JSON they must be encoded as n. The model inserts actual newlines.
  3. Quotes in HTML. class="pw-lead" inside a string closes it prematurely if not properly escaped.

The Dead End I Walked Into

Since JSON was breaking, I thought I’d abandon it and ask for a delimiter format: ===TITLE===, ===HTML=== and so on. I wrote a parser, deployed it, ran it. The model still returned JSON.

The reason turned out to be trivial but worth remembering: the function handling the request was forcing JSON mode at the API level. The model had no choice - my instruction about delimiters was ignored because the format was imposed at a lower layer. I wasted three runs on this before I checked what the function itself was doing, instead of tweaking the prompt.

Takeaway: when a model stubbornly ignores your format instruction, first check whether something in the transport layer is forcing a different one.

What Worked

A two-stage parser. First, regular `json_decode`. When that fails - extracting fields character by character, handling escape sequences like n, t, and uXXXX, stopping at the first unescaped quote.

The key property: this parser recovers content from truncated responses. If the JSON was cut off after the article field, all earlier fields are complete and can be read. A standard decoder rejects everything.

json_decode Tolerant parser
Valid JSON works works
Truncated at limit null recovers complete fields
Raw newline characters null recovers
Garbage before object null recovers

After this change, the same automation passed on the first attempt and produced a 1,228-word article.

Four Things I Now Always Do

  1. Log the end of the response, not the beginning. The beginning always looks fine. Only the last 100 characters tell you whether the response was truncated.
  2. Long content doesn’t go in JSON. If a field is supposed to contain more than a few hundred words, a separate call or a format without escaping is better.
  3. Validate content, not just format. Just because JSON parsed doesn’t mean the content makes sense - I have a separate test that checks whether all expected numbers are in the text.
  4. Three attempts, then log it. After three failures, the model won’t “fix itself” on the fourth try. Better to record the failure than publish garbage.

Why a Separate Category of Tools Is Emerging

This problem is common enough that models have started appearing that don’t generate text at all. Instead of asking a model for JSON and parsing it, you ask a typed question and get back a value with a probability distribution - no text stage, so no parsing stage.

I described one such case separately: TypeSafe and the Jev model, where the API has three question types and returns decisions instead of strings. This is an observation, not a recommendation - I haven’t tested it on my own task yet. But the direction is logical: if the output is meant for code to consume, the intermediate text stage is a cost, not a feature.

When to Use Which Approach

Situation What to choose
Short response, few fields JSON mode is enough
Long content in a field Separate call for content plus tolerant parser
Classification, scoring, choice from list Model returning type instead of text
Thousands of small decisions Decision model - cost and time make the difference there

Common Questions

Why does the model return invalid JSON when I asked for valid JSON?

Because underneath it generates sequential tokens, not structure. JSON mode improves reliability but doesn’t guarantee it - especially when the response gets truncated at the limit.

Do structured outputs solve the problem?

They help a lot with short and medium responses. With long content in a single field, there’s still a risk of truncation.

How do I check if a response was truncated?

Log its last 100 characters and length. A truncated response ends mid-sentence or mid-string, without closing.

The model ignores my format. What do I do?

Check whether the transport layer is forcing a different one. In my case, the wrapper function was imposing JSON mode at the API level - the instruction in the prompt had no chance.

Google · Your sourcesQuantslant higher in your Google - one clickAdd to preferred sources →

How I Know This

All cases come from building an editorial automation on this site in September 2026. Five failed runs, three different causes, each diagnosed from logs. The two-stage parser is running in production - the first article after the fix passed on the first attempt and had 1,228 words.

Kinetyra

// read next

More on this from QUANTSLANT

Piotr Olszewski

Editor and publisher · Quantslant

I edit and publish Quantslant. AI tools help with research, drafts and translation; choosing topics, running the tests, checking facts and the decision to publish are mine, and so is the responsibility for what appears here.

read nextLM Studio & MCP: How Much VRAM You Really Need for Local AIguidesLM Studio & MCP: How Much VRAM You Really Need for Local AIMemory thresholds from 8 to 24 GB, what you can actually run at each level, and four things local models won't do that tutorials don't mention.

Most read

  1. 1Best AI Video Generator Apps for Your Phone (2026)
  2. 2Claude Opus 5.5 Motion Graphics: 60s Showreel from One Prompt
  3. 3Wan 2.2 on RTX 4090: timings, VRAM and four test clips
// site map

This page in other languages1

Quantslant in numbers 1,423posts in archive22news from the last 7 days18video models in the observatory
🎬 AI Video Benchmarks: Higgsfield DoP vs OpenAI Sora • Higgsfield vs Runway Gen-3 • AI Video Credit Calculator
× ‹ enlarge ›