Skip to content

Instantly share code, notes, and snippets.

@AndyCross
Created December 11, 2025 15:01
Show Gist options
  • Select an option

  • Save AndyCross/86a29ad97f8f24fb37b61f8bff72f4a0 to your computer and use it in GitHub Desktop.

Select an option

Save AndyCross/86a29ad97f8f24fb37b61f8bff72f4a0 to your computer and use it in GitHub Desktop.
Partial Json Parser
/**
* Partial JSON parser for streaming LLM responses.
* Extracts the "response" field from incomplete JSON as it streams in.
*
* Expected format (wrapped in markdown code fences):
* ```json
* {"response": "...", "language": "...", "suggestedCasetas": [...], "suggestedMenus": [...]}
* ```
*/
/**
* Extract the response text from a partial JSON string.
* Handles the markdown code fence wrapper and incomplete JSON.
*/
export function extractResponseFromPartialJson(partialText: string): string | null {
// Remove markdown code fences if present
let json = partialText;
// Strip ```json prefix
const jsonStart = json.indexOf('```json');
if (jsonStart !== -1) {
json = json.substring(jsonStart + 7);
}
// Strip trailing ``` if present
const jsonEnd = json.lastIndexOf('```');
if (jsonEnd !== -1) {
json = json.substring(0, jsonEnd);
}
json = json.trim();
if (!json) return null;
// Find the "response" field
const responseKeyIndex = json.indexOf('"response"');
if (responseKeyIndex === -1) return null;
// Find the colon after "response"
const colonIndex = json.indexOf(':', responseKeyIndex + 10);
if (colonIndex === -1) return null;
// Find the opening quote of the value
const valueStart = json.indexOf('"', colonIndex);
if (valueStart === -1) return null;
// Extract the string value, handling escape sequences
let result = '';
let i = valueStart + 1;
let escaped = false;
while (i < json.length) {
const char = json[i];
if (escaped) {
// Handle escape sequences
switch (char) {
case 'n': result += '\n'; break;
case 'r': result += '\r'; break;
case 't': result += '\t'; break;
case '"': result += '"'; break;
case '\\': result += '\\'; break;
case '/': result += '/'; break;
case 'b': result += '\b'; break;
case 'f': result += '\f'; break;
case 'u':
// Unicode escape \uXXXX
if (i + 4 < json.length) {
const hex = json.substring(i + 1, i + 5);
const code = parseInt(hex, 16);
if (!isNaN(code)) {
result += String.fromCharCode(code);
i += 4;
}
}
break;
default:
// Unknown escape, just add the char
result += char;
}
escaped = false;
} else if (char === '\\') {
escaped = true;
} else if (char === '"') {
// End of string value - this is the complete response
break;
} else {
result += char;
}
i++;
}
return result;
}
/**
* Try to parse complete JSON and extract structured data.
* Returns null if JSON is not complete/valid.
*/
export interface ParsedResponse {
response: string;
language?: string;
suggestedCasetas?: Array<{ id: string; name: string; number: string }>;
suggestedMenus?: Array<{ id: string; name: string; price?: string; casetaId?: string }>;
}
export function tryParseCompleteJson(text: string): ParsedResponse | null {
// Remove markdown code fences
let json = text;
const jsonStart = json.indexOf('```json');
if (jsonStart !== -1) {
json = json.substring(jsonStart + 7);
}
const jsonEnd = json.lastIndexOf('```');
if (jsonEnd !== -1) {
json = json.substring(0, jsonEnd);
}
json = json.trim();
if (!json) return null;
try {
const parsed = JSON.parse(json);
if (typeof parsed.response === 'string') {
return {
response: parsed.response,
language: parsed.language,
suggestedCasetas: parsed.suggestedCasetas,
suggestedMenus: parsed.suggestedMenus,
};
}
} catch {
// JSON not complete yet
}
return null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment