Search IOCombats

Search challenges, guides, questions and articles

JSON.parse fails until the stream ends. What approaches exist for parsing partial JSON while a structured response streams?

Advanced12 min interview
Skills tested:
Explaining why JSON.parse cannot read an incomplete documentComparing repair-and-parse, element streaming and line-delimited formatsUsing partialOutputStream, elementStream and useObject from the AI SDKRendering DeepPartial data safely in ReactValidating the final object after the stream completes

Advertisement

🧩 Scenario

You are building a "generate a 10 day trip itinerary" feature. The full JSON takes a while to arrive, and the product manager wants day 1 on screen as soon as possible. The interviewer asks how you would parse and render the JSON while it streams.

Concept Walkthrough

Why JSON.parse cannot help

JSON.parse needs a complete document. Mid-stream you have something like {"days":[{"title":"Jai. It is valid JSON with a few closing characters missing. You need a way to read it early.

Option 1: wait for the end

Collect the text and parse once. Simple and fully validated. Fine for small objects, poor for long lists.

Option 2: repair, then parse (on every chunk)

Keep an accumulated string. On each chunk, close any open string, array and object, then parse the result. {"days":[{"title":"Jai becomes {"days":[{"title":"Jai"}]}. This is what the AI SDK does. Its parsePartialJson helper tries a normal parse first, then a repaired parse, and returns a state such as successful-parse or repaired-parse. The useObject hook calls it on every chunk and only updates state when the parsed value actually changed, a bit like a useMemo with a deep equality check.

The catch: the last string field is often half finished. "Jai" is not a city. Render it as in-progress text, but do not put a half URL into an href or a half number into a calculation.

Option 3: stream complete elements

If the output is a list, you often care about whole items, not half items. With streamText and Output.array, the AI SDK's elementStream emits each element only when it is complete and validated against the element schema. You get "day 1 is ready" instead of "day 1 title is 3 characters long".

Option 4: change the format

Ask for one JSON object per line (often called JSON Lines). Split on newlines, JSON.parse each full line. Very simple to parse, but you lose provider-side schema enforcement for the whole document, so you validate each line yourself.

Validate at the end

Partial objects cannot match a schema with required fields. Type them as deeply partial (the SDK uses a DeepPartial type), render with optional chaining, and validate the final object. useObject does that final check and passes { object, error } to onFinish.


Key Code Explained

// app/api/itinerary/schema.ts
import { z } from 'zod';

export const daySchema = z.object({
  title: z.string(),
  items: z.array(z.string()),
});
export const itinerarySchema = z.object({ days: z.array(daySchema) });
// app/api/itinerary/route.ts
import { streamText, Output, createTextStreamResponse, toTextStream } from 'ai';
import { chatModel } from '@/lib/models';
import { itinerarySchema } from './schema';

export async function POST(req: Request) {
  const { city }: { city: string } = await req.json();

  const result = streamText({
    model: chatModel,
    output: Output.object({ schema: itinerarySchema }),
    prompt: `Plan a 10 day trip starting in ${city}.`,
  });

  // Streams the raw JSON text. The client repairs and parses it.
  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
// app/itinerary/page.tsx
'use client';
import { useObject } from '@ai-sdk/react';
import { itinerarySchema } from '../api/itinerary/schema';

export default function Itinerary() {
  const { object, submit, isLoading, stop } = useObject({
    api: '/api/itinerary',
    schema: itinerarySchema, // used for the final check, not mid-stream
    onFinish: ({ error }) => {
      if (error) console.error('Final object failed validation', error);
    },
  });

  return (
    <div>
      <button onClick={() => submit({ city: 'Jaipur' })}>Plan trip</button>
      {isLoading && <button onClick={stop}>Stop</button>}
      {object?.days?.map((day, i) => (
        <section key={i}>
          <h3>{day?.title ?? '...'}</h3>
          <ul>
            {day?.items?.map((item, j) => <li key={j}>{item}</li>)}
          </ul>
        </section>
      ))}
    </div>
  );
}

Every access uses ?. because any level may not exist yet. On the server, if you need partial objects yourself (for example to push progress into your own stream), iterate result.partialOutputStream.


Tradeoffs

ApproachFirst paintValidationComplexity
Wait for full textSlowFull, onceLowest
Repair and parseFastestOnly at the endLow with an SDK
elementStream (arrays)FastEach element validatedLow
JSON LinesFastPer line, by youMedium (prompt must hold the format)

What Interviewers Actually Check

  • Whether you know why JSON.parse fails mid-stream
  • Whether you can name more than one approach and pick by use case
  • Whether you handle the half-finished last field
  • Whether you type partial data as deeply partial and render defensively
  • Whether final validation still happens, and where

Follow-Up Questions

  1. How would you show a field as "still typing" versus "done"?
  2. What happens to keys in a list when items arrive one by one? Is the index safe here?
  3. How would you cancel the stream and keep what has arrived?
  4. How does re-parsing the whole buffer on every chunk scale for very long output?
  5. When would you rather stream text and a separate final object?

Common Candidate Mistakes

  • Swallowing parse errors on each chunk and rendering nothing until the end
  • Using regex to extract values from incomplete JSON
  • Treating a partial string as final data
  • Running full schema validation on every partial object
  • Re-rendering on every chunk without checking if the value changed

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you name four approaches to partial JSON and when each fits?
  • Can you repair a partial JSON string by hand?
  • Can you build the route and the useObject client above from memory?
  • Can you explain what elementStream guarantees?
  • Can you say where the final validation happens?

Summary

JSON.parse needs a complete document, so streaming structured output needs another plan. You can wait for the end, repair and parse on every chunk, stream whole validated elements, or use one JSON object per line. The AI SDK covers the common cases: partialOutputStream and useObject for repaired partial objects, elementStream for complete array items. Partial data cannot be validated against the full schema, so render it with optional chaining, be careful with half-finished fields, and validate the final object once the stream ends.

Frequently Asked Questions

Why not just wait for the full response?

For small objects that is often the right call. For long lists or reports, users stare at a spinner for many seconds. Streaming partial objects lets the first card appear early, which feels much faster.

Can I validate a partial object with Zod?

Not against the full schema, because required fields may not have arrived yet. The AI SDK docs say partial outputs cannot be validated. Render defensively during the stream and validate the final object.

Is re-parsing the whole text on every chunk slow?

It grows with the length of the text, so very long outputs do more work per chunk. Compare the result with the previous one and only update state when it changed, and throttle renders if needed.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Namaste Dev
Sponsored

Cohort-style courses to go from tutorials to job-ready.

Explore Courses

Advertisement