assistant-stream
Version:
Streaming utilities for AI assistants
297 lines (296 loc) • 7.51 kB
JavaScript
// src/utils/json/fix-json.ts
const HEX_DIGIT = /[0-9a-fA-F]/;
function fixJson(input) {
const stack = ["ROOT"];
let lastValidIndex = -1;
let literalStart = null;
let unicodeEscapeDigits = 0;
const path = [];
let currentKey;
function pushCurrentKeyToPath() {
if (currentKey !== void 0) {
path.push(JSON.parse(`"${currentKey}"`));
currentKey = void 0;
}
}
function processValueStart(char, i, swapState) {
switch (char) {
case "\"":
lastValidIndex = i;
stack.pop();
stack.push(swapState);
stack.push("INSIDE_STRING");
pushCurrentKeyToPath();
break;
case "f":
case "t":
case "n":
lastValidIndex = i;
literalStart = i;
stack.pop();
stack.push(swapState);
stack.push("INSIDE_LITERAL");
break;
case "-":
stack.pop();
stack.push(swapState);
stack.push("INSIDE_NUMBER");
pushCurrentKeyToPath();
break;
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
lastValidIndex = i;
stack.pop();
stack.push(swapState);
stack.push("INSIDE_NUMBER");
pushCurrentKeyToPath();
break;
case "{":
lastValidIndex = i;
stack.pop();
stack.push(swapState);
stack.push("INSIDE_OBJECT_START");
pushCurrentKeyToPath();
break;
case "[":
lastValidIndex = i;
stack.pop();
stack.push(swapState);
stack.push("INSIDE_ARRAY_START");
pushCurrentKeyToPath();
}
}
function processAfterObjectValue(char, i) {
switch (char) {
case ",":
stack.pop();
stack.push("INSIDE_OBJECT_AFTER_COMMA");
break;
case "}":
lastValidIndex = i;
stack.pop();
currentKey = path.pop();
}
}
function processAfterArrayValue(char, i) {
switch (char) {
case ",":
stack.pop();
stack.push("INSIDE_ARRAY_AFTER_COMMA");
currentKey = (Number(currentKey) + 1).toString();
break;
case "]":
lastValidIndex = i;
stack.pop();
currentKey = path.pop();
}
}
for (let i = 0; i < input.length; i++) {
const char = input[i];
switch (stack[stack.length - 1]) {
case "ROOT":
processValueStart(char, i, "FINISH");
break;
case "INSIDE_OBJECT_START":
switch (char) {
case "\"":
stack.pop();
stack.push("INSIDE_OBJECT_KEY");
currentKey = "";
break;
case "}":
lastValidIndex = i;
stack.pop();
currentKey = path.pop();
}
break;
case "INSIDE_OBJECT_AFTER_COMMA":
switch (char) {
case "\"":
stack.pop();
stack.push("INSIDE_OBJECT_KEY");
currentKey = "";
}
break;
case "INSIDE_OBJECT_KEY":
switch (char) {
case "\"":
stack.pop();
stack.push("INSIDE_OBJECT_AFTER_KEY");
break;
case "\\":
stack.push("INSIDE_STRING_ESCAPE");
currentKey += char;
break;
default: currentKey += char;
}
break;
case "INSIDE_OBJECT_AFTER_KEY":
switch (char) {
case ":":
stack.pop();
stack.push("INSIDE_OBJECT_BEFORE_VALUE");
}
break;
case "INSIDE_OBJECT_BEFORE_VALUE":
processValueStart(char, i, "INSIDE_OBJECT_AFTER_VALUE");
break;
case "INSIDE_OBJECT_AFTER_VALUE":
processAfterObjectValue(char, i);
break;
case "INSIDE_STRING":
switch (char) {
case "\"":
stack.pop();
lastValidIndex = i;
currentKey = path.pop();
break;
case "\\":
stack.push("INSIDE_STRING_ESCAPE");
break;
default: lastValidIndex = i;
}
break;
case "INSIDE_ARRAY_START":
switch (char) {
case "]":
lastValidIndex = i;
stack.pop();
currentKey = path.pop();
break;
default:
currentKey = "0";
processValueStart(char, i, "INSIDE_ARRAY_AFTER_VALUE");
}
break;
case "INSIDE_ARRAY_AFTER_VALUE":
switch (char) {
case ",":
stack.pop();
stack.push("INSIDE_ARRAY_AFTER_COMMA");
currentKey = (Number(currentKey) + 1).toString();
break;
case "]":
lastValidIndex = i;
stack.pop();
currentKey = path.pop();
break;
default: lastValidIndex = i;
}
break;
case "INSIDE_ARRAY_AFTER_COMMA":
processValueStart(char, i, "INSIDE_ARRAY_AFTER_VALUE");
break;
case "INSIDE_STRING_ESCAPE": {
stack.pop();
const parent = stack[stack.length - 1];
if (char === "u") {
stack.push("INSIDE_STRING_UNICODE_ESCAPE");
unicodeEscapeDigits = 0;
} else if (parent === "INSIDE_STRING") lastValidIndex = i;
if (parent === "INSIDE_OBJECT_KEY") currentKey += char;
break;
}
case "INSIDE_STRING_UNICODE_ESCAPE": {
const parent = stack[stack.length - 2];
if (!HEX_DIGIT.test(char)) {
stack.pop();
i--;
break;
}
unicodeEscapeDigits++;
if (unicodeEscapeDigits === 4) {
stack.pop();
if (parent === "INSIDE_STRING") lastValidIndex = i;
}
if (parent === "INSIDE_OBJECT_KEY") currentKey += char;
break;
}
case "INSIDE_NUMBER":
switch (char) {
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
lastValidIndex = i;
break;
case "e":
case "E":
case "-":
case "+":
case ".": break;
case ",":
stack.pop();
currentKey = path.pop();
if (stack[stack.length - 1] === "INSIDE_ARRAY_AFTER_VALUE") processAfterArrayValue(char, i);
if (stack[stack.length - 1] === "INSIDE_OBJECT_AFTER_VALUE") processAfterObjectValue(char, i);
break;
case "}":
stack.pop();
currentKey = path.pop();
if (stack[stack.length - 1] === "INSIDE_OBJECT_AFTER_VALUE") processAfterObjectValue(char, i);
break;
case "]":
stack.pop();
currentKey = path.pop();
if (stack[stack.length - 1] === "INSIDE_ARRAY_AFTER_VALUE") processAfterArrayValue(char, i);
break;
default:
stack.pop();
currentKey = path.pop();
}
break;
case "INSIDE_LITERAL": {
const partialLiteral = input.substring(literalStart, i + 1);
if (!"false".startsWith(partialLiteral) && !"true".startsWith(partialLiteral) && !"null".startsWith(partialLiteral)) {
stack.pop();
if (stack[stack.length - 1] === "INSIDE_OBJECT_AFTER_VALUE") processAfterObjectValue(char, i);
else if (stack[stack.length - 1] === "INSIDE_ARRAY_AFTER_VALUE") processAfterArrayValue(char, i);
} else lastValidIndex = i;
break;
}
}
}
let result = input.slice(0, lastValidIndex + 1);
for (let i = stack.length - 1; i >= 0; i--) switch (stack[i]) {
case "INSIDE_STRING":
result += "\"";
break;
case "INSIDE_OBJECT_KEY":
case "INSIDE_OBJECT_AFTER_KEY":
case "INSIDE_OBJECT_AFTER_COMMA":
case "INSIDE_OBJECT_START":
case "INSIDE_OBJECT_BEFORE_VALUE":
case "INSIDE_OBJECT_AFTER_VALUE":
result += "}";
break;
case "INSIDE_ARRAY_START":
case "INSIDE_ARRAY_AFTER_COMMA":
case "INSIDE_ARRAY_AFTER_VALUE":
result += "]";
break;
case "INSIDE_LITERAL": {
const partialLiteral = input.substring(literalStart, input.length);
if ("true".startsWith(partialLiteral)) result += "true".slice(partialLiteral.length);
else if ("false".startsWith(partialLiteral)) result += "false".slice(partialLiteral.length);
else if ("null".startsWith(partialLiteral)) result += "null".slice(partialLiteral.length);
}
}
return [result, path];
}
export { fixJson };
//# sourceMappingURL=fix-json.js.map