npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

loot-json

v0.5.0

Published

Don't just parse. Loot it. The ultimate JSON extractor for unstable LLM outputs.

Readme

💎 loot-json

Don't just parse. Loot it. The ultimate JSON extractor for unstable LLM outputs.

npm version npm downloads bundle size license

loot-json is a robust TypeScript library designed to salvage valid JSON objects from the messy, unpredictable text generated by Large Language Models (LLMs). Whether it's buried in Markdown, mixed with Chain-of-Thought reasoning, or structurally broken—we loot it.

✨ Features

  • 🛡️ Durable: Extracts JSON from Markdown blocks, plain text, or mixed content
  • 🔧 Forgiving: Auto-repairs common LLM syntax errors (trailing commas, single quotes, comments)
  • 📦 Zero Dependency: Lightweight and fast
  • 🇹 Type-Safe: Full TypeScript generics support
  • Streaming: Incremental parsing for real-time field extraction
  • 🔍 Field Extraction: Extract specific fields without parsing the entire document
  • Validation: Built-in JSON Schema validation

📦 Installation

npm install loot-json

🚀 Quick Start

import { loot } from 'loot-json';

const dirtyText = `
  Sure! I found the item for you.
  \`\`\`json
  {
    "id": "sword_01",
    "damage": 50, // It's strong!
  }
  \`\`\`
`;

const item = loot(dirtyText);
console.log(item); // { id: "sword_01", damage: 50 }

📖 API Reference

loot<T>(text, options?)

Extract and parse JSON from text.

// Basic usage
const data = loot<Item>(text);

// With options
const data = loot(text, {
  silent: true,      // Returns null instead of throwing
  repair: true,      // Auto-repair malformed JSON (default: true)
  all: true,         // Extract all JSON objects found
  reportRepairs: true // Return repair logs
});

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | silent | boolean | false | Return null instead of throwing on failure | | repair | boolean | true | Attempt to repair malformed JSON | | all | boolean | false | Extract all JSON objects found | | reportRepairs | boolean | false | Include repair logs in result |

Examples

// Extract all JSON objects
const items = loot(text, { all: true });
// [{ id: 1 }, { id: 2 }, { id: 3 }]

// Silent mode (no throwing)
const result = loot(maybeHasJson, { silent: true });
if (result === null) {
  console.log('No JSON found');
}

// With repair report
const { result, repairs } = loot(text, { reportRepairs: true });
console.log(repairs);
// [{ type: 'trailing_comma', position: 45, description: '...', fixed: true }]

Error Handling with isLootError

Type-safe error handling for loot operations.

import { loot, isLootError } from 'loot-json';

try {
  const output = getLLMOutput();
  const data = loot<MySchema>(output);
  // use data...
} catch (error) {
  if (isLootError(error)) {
    // TypeScript knows error is LootError
    console.log('Loot failed:', error.code, error.message);
    
    switch (error.code) {
      case 'EMPTY_INPUT':
        // Handle empty input
        break;
      case 'NO_JSON_FOUND':
        // Handle no JSON found
        break;
      case 'PARSE_FAILED':
        // Handle parse failure
        break;
    }
  }
}

Error Codes

| Code | Description | |------|-------------| | EMPTY_INPUT | Input text is empty or not a string | | NO_JSON_FOUND | No valid JSON found in text | | PARSE_FAILED | JSON parsing failed even after repair | | FIELD_NOT_FOUND | Requested field not found (lootField) | | VALIDATION_FAILED | Schema validation failed |


lootField<T>(text, path, options?)

Extract a specific field without parsing the entire document.

import { lootField } from 'loot-json';

// Simple field
const name = lootField<string>(text, 'name');

// Nested field (dot notation)
const city = lootField(text, 'user.address.city');

// Bracket notation for special keys
const value = lootField(text, 'data["special.key"]');

IncrementalLoot<T>

Streaming JSON parser for real-time field extraction during LLM streaming.

import { IncrementalLoot } from 'loot-json';

interface ChatResponse {
  dialogue: string;
  emotion: string;
  pose: string;
}

const parser = new IncrementalLoot<ChatResponse>({
  fields: ['dialogue', 'emotion', 'pose'],
  onFieldComplete: (field, value) => {
    if (field === 'dialogue') {
      startTTS(value as string); // Start TTS immediately!
    }
  },
});

for await (const chunk of llmStream) {
  const result = parser.addChunk(chunk);
  
  if (result.isFieldComplete('dialogue')) {
    console.log('Dialogue ready:', result.getField('dialogue'));
  }
  
  if (result.isComplete()) {
    break;
  }
}

const finalResult = parser.getResult();

validate<T>(data, schema)

Validate data against a JSON Schema (subset of Draft-07).

import { validate } from 'loot-json';

const result = validate(data, {
  type: 'object',
  properties: {
    dialogue: { type: 'string', minLength: 1 },
    emotion: { type: 'string', enum: ['happy', 'sad', 'angry'] },
    affinity: { type: 'number', minimum: -10, maximum: 10 },
  },
  required: ['dialogue', 'emotion'],
});

if (result.valid) {
  console.log('Valid:', result.data);
} else {
  console.log('Errors:', result.errors);
}

Supported Schema Keywords

| Category | Keywords | |----------|----------| | Type | type (string, number, integer, boolean, object, array, null) | | String | minLength, maxLength, pattern, format | | Number | minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf | | Object | properties, required, additionalProperties | | Array | items, minItems, maxItems, uniqueItems | | Enum | enum, const |


repairJson(text, trackRepairs?)

Repair malformed JSON without extraction.

import { repairJson } from 'loot-json';

// Simple repair
const fixed = repairJson('{"key": "value",}');
// '{"key": "value"}'

// With repair tracking
const { text, repairs } = repairJson('{"key": "value",}', true);
console.log(repairs);
// [{ type: 'trailing_comma', ... }]

🔧 What It Fixes

| Issue | Example | Fixed | |-------|---------|-------| | Trailing commas | { "a": 1, } | { "a": 1 } | | Single quotes | { 'key': 'value' } | { "key": "value" } | | Comments | { "a": 1 // comment } | { "a": 1 } | | Unquoted keys | { key: "value" } | { "key": "value" } | | Invalid values | { "a": undefined } | { "a": null } | | Unescaped newlines | { "a": "line1\nline2" } | { "a": "line1\\nline2" } |


🏗️ Advanced Usage

Utility Functions

import { 
  findJsonCandidates, 
  extractFromMarkdown, 
  extractByBraces 
} from 'loot-json';

// Find all JSON candidates in text
const candidates = findJsonCandidates(messyText);

// Extract from markdown code blocks only
const fromMarkdown = extractFromMarkdown(text);

// Extract by balanced braces
const byBraces = extractByBraces(text);

📊 Use Cases

1. LLM Response Parsing

const llmResponse = await openai.chat.completions.create({ ... });
const data = loot<ResponseSchema>(llmResponse.choices[0].message.content);

2. Streaming with Early TTS

const parser = new IncrementalLoot({
  fields: ['dialogue'],
  onFieldComplete: (field, value) => {
    if (field === 'dialogue') {
      ttsEngine.speak(value); // Start speaking before full response!
    }
  },
});

for await (const chunk of stream) {
  parser.addChunk(chunk.choices[0].delta.content || '');
}

3. Structured Output Validation

const { result, repairs } = loot(llmOutput, { reportRepairs: true });

if (repairs.length > 0) {
  logger.warn('LLM output required repairs:', repairs);
}

const validated = validate(result, mySchema);
if (!validated.valid) {
  logger.error('Schema validation failed:', validated.errors);
}

📄 License

MIT © 2025


🤝 Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.