UNPKG

best-effort-json-parser

Version:
239 lines 6.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parse = exports.enableErrorLogging = exports.disableErrorLogging = exports.setErrorLogger = void 0; let logError = console.error; // for testing (spy/mock) function setErrorLogger(logger) { logError = logger; } exports.setErrorLogger = setErrorLogger; function disableErrorLogging() { logError = () => { /* do not output to console */ }; } exports.disableErrorLogging = disableErrorLogging; function enableErrorLogging() { logError = console.error; } exports.enableErrorLogging = enableErrorLogging; function parse(s) { if (s === undefined) { return undefined; } if (s === null) { return null; } if (s === '') { return ''; } // remove incomplete escaped characters at the end of the string s = s.replace(/\\+$/, match => match.length % 2 === 0 ? match : match.slice(0, -1)); try { return JSON.parse(s); } catch (e) { const [data, reminding] = s.trimLeft()[0] === ':' ? parseAny(s, e) : parseAny(s, e, parseStringWithoutQuote); parse.lastParseReminding = reminding; if (parse.onExtraToken && reminding.length > 0) { const trimmedReminding = reminding.trimRight(); parse.lastParseReminding = trimmedReminding; if (trimmedReminding.length > 0) { parse.onExtraToken(s, data, trimmedReminding); } } return data; } } exports.parse = parse; (function (parse) { parse.onExtraToken = (text, data, reminding) => { logError('parsed json with extra tokens:', { text, data, reminding, }); }; })(parse = exports.parse || (exports.parse = {})); function parseAny(s, e, fallback) { const parser = parsers[s[0]] || fallback; if (!parser) { logError(`no parser registered for ${JSON.stringify(s[0])}:`, { s }); throw e; } return parser(s, e); } function parseStringCasual(s, e, delimiters) { if (s[0] === '"') { return parseString(s); } if (s[0] === "'") { return parseSingleQuoteString(s); } return parseStringWithoutQuote(s, e, delimiters); } const parsers = {}; function skipSpace(s) { return s.trimLeft(); } parsers[' '] = parseSpace; parsers['\r'] = parseSpace; parsers['\n'] = parseSpace; parsers['\t'] = parseSpace; function parseSpace(s, e) { s = skipSpace(s); return parseAny(s, e); } parsers['['] = parseArray; function parseArray(s, e) { s = s.substr(1); // skip starting '[' const acc = []; s = skipSpace(s); for (; s.length > 0;) { if (s[0] === ']') { s = s.substr(1); // skip ending ']' break; } const res = parseAny(s, e, (s, e) => parseStringWithoutQuote(s, e, [',', ']'])); acc.push(res[0]); s = res[1]; s = skipSpace(s); if (s[0] === ',') { s = s.substring(1); s = skipSpace(s); } } return [acc, s]; } for (const c of '0123456789.-'.slice()) { parsers[c] = parseNumber; } function parseNumber(s) { for (let i = 0; i < s.length; i++) { const c = s[i]; if (parsers[c] === parseNumber) { continue; } const num = s.substring(0, i); s = s.substring(i); return [numToStr(num), s]; } return [numToStr(s), '']; } function numToStr(s) { if (s === '-') { return -0; } const num = +s; if (Number.isNaN(num)) { return s; } return num; } parsers['"'] = parseString; function parseString(s) { for (let i = 1; i < s.length; i++) { const c = s[i]; if (c === '\\') { i++; continue; } if (c === '"') { const str = fixEscapedCharacters(s.substring(0, i + 1)); s = s.substring(i + 1); return [JSON.parse(str), s]; } } return [JSON.parse(fixEscapedCharacters(s) + '"'), '']; } function fixEscapedCharacters(s) { return s.replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/\r/g, '\\r'); } parsers["'"] = parseSingleQuoteString; function parseSingleQuoteString(s) { for (let i = 1; i < s.length; i++) { const c = s[i]; if (c === '\\') { i++; continue; } if (c === "'") { const str = fixEscapedCharacters(s.substring(0, i + 1)); s = s.substring(i + 1); return [JSON.parse('"' + str.slice(1, -1) + '"'), s]; } } return [JSON.parse('"' + fixEscapedCharacters(s.slice(1)) + '"'), '']; } function parseStringWithoutQuote(s, e, delimiters = [' ']) { const index = Math.min(...delimiters.map(delimiter => { const index = s.indexOf(delimiter); return index === -1 ? s.length : index; })); const value = s.substring(0, index).trim(); const rest = s.substring(index); return [value, rest]; } parsers['{'] = parseObject; function parseObject(s, e) { s = s.substr(1); // skip starting '{' const acc = {}; s = skipSpace(s); for (; s.length > 0;) { if (s[0] === '}') { s = s.substr(1); // skip ending '}' break; } const keyRes = parseStringCasual(s, e, [':', '}']); const key = keyRes[0]; s = keyRes[1]; s = skipSpace(s); if (s[0] !== ':') { acc[key] = undefined; break; } s = s.substr(1); // skip ':' s = skipSpace(s); if (s.length === 0) { acc[key] = undefined; break; } const valueRes = parseAny(s, e); acc[key] = valueRes[0]; s = valueRes[1]; s = skipSpace(s); if (s[0] === ',') { s = s.substr(1); s = skipSpace(s); } } return [acc, s]; } parsers['t'] = parseTrue; function parseTrue(s, e) { return parseToken(s, `true`, true, e); } parsers['f'] = parseFalse; function parseFalse(s, e) { return parseToken(s, `false`, false, e); } parsers['n'] = parseNull; function parseNull(s, e) { return parseToken(s, `null`, null, e); } function parseToken(s, tokenStr, tokenVal, e) { for (let i = tokenStr.length; i >= 1; i--) { if (s.startsWith(tokenStr.slice(0, i))) { return [tokenVal, s.slice(i)]; } } /* istanbul ignore next */ { const prefix = JSON.stringify(s.slice(0, tokenStr.length)); logError(`unknown token starting with ${prefix}:`, { s }); throw e; } } //# sourceMappingURL=parse.js.map