ai-json-fixer
Version:
A simple JSON parser designed to handle malformed JSON from Large Language Models
190 lines • 6.99 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LLMJSONParser = void 0;
const markdown_extraction_1 = require("../fixes/markdown-extraction");
const trailing_content_removal_1 = require("../fixes/trailing-content-removal");
const quote_fixing_1 = require("../fixes/quote-fixing");
const missing_comma_detection_1 = require("../fixes/missing-comma-detection");
/**
* Main parser class for handling malformed JSON from Large Language Models
*/
class LLMJSONParser {
constructor() {
this.defaultOptions = {
mode: 'standard',
stripMarkdown: true,
trimTrailing: true,
fixQuotes: true,
addMissingCommas: true,
completeStructure: false,
coerceTypes: false,
escapeCharacters: false,
trackFixes: false,
throwOnError: false,
maxFixAttempts: 3,
};
}
/**
* Parse JSON input with automatic fixing of common LLM output issues
*/
parse(input, options) {
const opts = { ...this.defaultOptions, ...options };
const result = this.tryParse(input, options);
if (result.data === null && opts.throwOnError) {
throw new Error(`Failed to parse JSON: ${result.warnings?.join(', ') || 'Unknown error'}`);
}
return result.data;
}
/**
* Try to parse JSON input, returning detailed results including fixes and warnings
*/
tryParse(input, options) {
const opts = { ...this.defaultOptions, ...options };
const fixes = [];
const warnings = [];
// Handle empty input
if (!input || input.trim() === '') {
return {
data: null,
fixes: fixes,
warnings: ['Input is empty'],
};
}
let processedInput = input;
let attemptCount = 0;
while (attemptCount < opts.maxFixAttempts) {
attemptCount++;
try {
// Try parsing as-is first
const parsed = JSON.parse(processedInput);
return {
data: parsed,
fixes: opts.trackFixes ? fixes : undefined,
confidence: this.calculateConfidence(fixes.length),
warnings: warnings.length > 0 ? warnings : undefined,
};
}
catch (parseError) {
// Apply fixes based on mode and options
const fixedInput = this.applyFixes(processedInput, opts, fixes);
if (fixedInput === processedInput) {
// No fixes were applied, parsing failed
warnings.push(`Parse attempt ${attemptCount} failed: ${parseError.message}`);
break;
}
processedInput = fixedInput;
}
}
// Final attempt after all fixes
try {
const parsed = JSON.parse(processedInput);
return {
data: parsed,
fixes: opts.trackFixes ? fixes : undefined,
confidence: this.calculateConfidence(fixes.length),
warnings: warnings.length > 0 ? warnings : undefined,
};
}
catch (finalError) {
warnings.push(`Final parse failed: ${finalError.message}`);
return {
data: null,
fixes: opts.trackFixes ? fixes : undefined,
confidence: 0,
warnings,
};
}
}
/**
* Apply fixes to the input based on options and mode
*/
applyFixes(input, options, fixes) {
let processed = input;
// 1. Extract from markdown blocks
if (options.stripMarkdown) {
const extracted = (0, markdown_extraction_1.extractFromMarkdown)(processed);
if (extracted !== processed) {
processed = extracted;
if (options.trackFixes) {
fixes.push({
type: 'markdown_stripped',
line: 0,
column: 0,
description: 'Extracted JSON from markdown code block',
});
}
}
}
// 2. Remove trailing content
if (options.trimTrailing) {
const trimmed = (0, trailing_content_removal_1.removeTrailingContent)(processed);
if (trimmed !== processed) {
processed = trimmed;
if (options.trackFixes) {
fixes.push({
type: 'trailing_removed',
line: 0,
column: 0,
description: 'Removed trailing content after JSON',
});
}
}
}
// 3. Fix unescaped quotes
if (options.fixQuotes) {
const quotesFixed = (0, quote_fixing_1.fixUnescapedQuotes)(processed);
if (quotesFixed !== processed) {
processed = quotesFixed;
if (options.trackFixes) {
fixes.push({
type: 'unescaped_quote',
line: 0,
column: 0,
description: 'Fixed unescaped quotes in strings',
});
}
}
}
// 4. Add missing commas
if (options.addMissingCommas) {
const commasAdded = (0, missing_comma_detection_1.addMissingCommas)(processed);
if (commasAdded !== processed) {
processed = commasAdded;
if (options.trackFixes) {
fixes.push({
type: 'missing_comma',
line: 0,
column: 0,
description: 'Added missing commas between elements',
});
}
}
}
// Mode-specific fixes
if (options.mode === 'aggressive') {
// In aggressive mode, apply additional fixes if needed
// For now, this is the same as standard mode
}
else if (options.mode === 'strict') {
// In strict mode, only apply fixes if they're explicitly enabled
// Current implementation already handles this via options
}
return processed;
}
/**
* Calculate confidence score based on number of fixes applied
*/
calculateConfidence(fixCount) {
if (fixCount === 0)
return 1.0;
if (fixCount === 1)
return 0.9;
if (fixCount === 2)
return 0.8;
if (fixCount === 3)
return 0.7;
return Math.max(0.5, 1.0 - fixCount * 0.1);
}
}
exports.LLMJSONParser = LLMJSONParser;
//# sourceMappingURL=llm-json-parser.js.map