@selaliadobor/incomplete-json-parser
Version:
A JSON parser that can parse incomplete JSON strings.
106 lines (105 loc) • 3.37 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LiteralScope = void 0;
const scope_interface_1 = require("./scope.interface");
const NUMBER_REGEX = /^-?\d+(\.\d*)?$/;
const PARTIAL_UNICODE_REGEX = /\\u[\da-fA-F]{0,3}$/;
const NULL_STR = "null";
const TRUE_STR = "true";
const FALSE_STR = "false";
class LiteralScope extends scope_interface_1.Scope {
content = "";
write(letter) {
if (this.finish) {
throw new Error("Literal already finished");
}
this.content += letter;
const assume = this.getOrAssume();
if (assume === undefined) {
this.content = this.content.slice(0, -1);
return false;
}
this.checkCompletion();
return true;
}
checkCompletion() {
const c = this.content;
const len = c.length;
if (len === 0)
return;
const firstChar = c[0];
switch (firstChar) {
case '"':
if (len >= 2 && c[len - 1] === '"' && !this.endsWithEscapedQuote()) {
this.finish = true;
}
break;
case 't':
if (c === TRUE_STR) {
this.finish = true;
}
break;
case 'f':
if (c === FALSE_STR) {
this.finish = true;
}
break;
case 'n':
if (c === NULL_STR) {
this.finish = true;
}
break;
}
}
getOrAssume() {
const c = this.content;
const len = c.length;
if (len === 0) {
return null;
}
const firstChar = c[0];
switch (firstChar) {
case 'n':
return len <= NULL_STR.length && NULL_STR.startsWith(c) ? null : undefined;
case 't':
return len <= TRUE_STR.length && TRUE_STR.startsWith(c) ? true : undefined;
case 'f':
return len <= FALSE_STR.length && FALSE_STR.startsWith(c) ? false : undefined;
case '"':
return this.parseStringLike();
case '-':
if (len === 1)
return 0;
default:
return NUMBER_REGEX.test(c) ? parseFloat(c) : undefined;
}
}
parseStringLike() {
const c = this.content;
let jsonedString = c;
const len = c.length;
const isCompleted = len >= 2 && c[len - 1] === '"' && !this.endsWithEscapedQuote();
if (!isCompleted) {
const unicodeMatch = PARTIAL_UNICODE_REGEX.exec(jsonedString);
if (unicodeMatch) {
jsonedString = jsonedString.slice(0, unicodeMatch.index);
}
if (jsonedString.endsWith("\\") && !jsonedString.endsWith("\\\\")) {
jsonedString = jsonedString.slice(0, -1);
}
jsonedString += '"';
}
try {
return JSON.parse(jsonedString);
}
catch {
return undefined;
}
}
endsWithEscapedQuote() {
const c = this.content;
const len = c.length;
return len >= 2 && c[len - 1] === '"' && c[len - 2] === '\\';
}
}
exports.LiteralScope = LiteralScope;