ai-json-parse
v0.1.0
Published
Extract and parse the first JSON block from LLM output — OpenAI, Claude, Gemini markdown fences or inline JSON. Zero deps.
Maintainers
Readme
ai-json-parse
Parse JSON from LLM text — even when the model wraps it in markdown, adds explanations, or embeds { ... } inside a sentence.
Zero dependencies · TypeScript types · ESM + CommonJS · ~3 KB minified
The problem
Models rarely return clean JSON. You often get prose + a ```json fenced block + a closing sentence.
JSON.parse(entireMessage) fails. Regex breaks on nested braces and strings. ai-json-parse finds the first valid JSON block and parses it.
Install
npm install ai-json-parseyarn add ai-json-parse
pnpm add ai-json-parseQuick start
import { parseFirstJson } from "ai-json-parse";
const reply = await callOpenAI(); // string from chat completion
const data = parseFirstJson<{ status: string; items: number[] }>(reply);
console.log(data.status); // "ok"Examples
Markdown json fence (most common)
Input (LLM output):
Here are the users:
```json
{"users": [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Lin"}]}
```Code:
import { parseFirstJson } from "ai-json-parse";
const users = parseFirstJson(llmText);
// { users: [{ id: 1, name: "Ada" }, { id: 2, name: "Lin" }] }Generic code fence
Input: a fenced block containing ["pending", "done", "failed"]
Output: ["pending", "done", "failed"]
Inline JSON in prose
Input:
Use this config for local dev: {"port": 3000, "debug": true} and restart the server.Output: { port: 3000, debug: true }
OpenAI / structured-style response
import OpenAI from "openai";
import { parseFirstJson } from "ai-json-parse";
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "user",
content: "Return JSON with fields city and temp_c only. No other text.",
},
],
});
const text = completion.choices[0]?.message?.content ?? "";
const weather = parseFirstJson<{ city: string; temp_c: number }>(text);Safe parsing (no throw)
import { tryParseFirstJson } from "ai-json-parse";
const data = tryParseFirstJson(maybeNotJson);
if (data === null) {
// fallback: retry prompt, log, etc.
}Raw JSON string only
import { extractFirstJsonString } from "ai-json-parse";
const raw = extractFirstJsonString(llmText);
// '{"users":[...]}' — validate or pass to another parser yourselfAPI
| Export | Description |
|--------|-------------|
| parseFirstJson<T>(text, options?) | Returns first parsed JSON value. Throws JsonExtractError if none found (default). |
| tryParseFirstJson<T>(text) | Same as parseFirstJson(text, { strict: false }) → T \| null. |
| extractFirstJsonString(text) | Returns raw JSON string; throws if not found. |
| JsonExtractError | Error when no block exists or JSON.parse fails. |
Options
parseFirstJson(text, { strict: false }); // returns null instead of throwingHow “first” is chosen
Candidates are sorted by position in the string (earliest wins). The first block that passes JSON.parse is returned.
```json ... ```fenced blocks- Other
``` ... ```blocks that start with{or[ - Inline balanced
{...}or[...](string-aware, handles\"and{inside strings)
Comparison
| Approach | LLM markdown + prose |
|----------|----------------------|
| JSON.parse(fullText) | Fails |
| Naive regex | Breaks on nested {} and strings |
| ai-json-parse | Handles fences + inline JSON |
| JSON5 / repair libs | Different goal (fix invalid JSON); use alongside if needed |
Limits
- Strict JSON only — no trailing commas, comments, or single-quoted keys.
- Returns the first valid block; later blocks are ignored.
- Not for security — this is developer ergonomics, not encryption or hiding data.
Requirements
- Node.js 18+
- Works in bundlers (Vite, webpack, Next.js) and serverless
Development
Clone and test locally:
git clone https://github.com/monil80/ai-json-parse.git
cd ai-json-parse
npm install
npm test
npm run build
npm run exampleRelated keywords
llm openai chatgpt claude gemini structured output json extract markdown code block agent function calling
