refactor: custom json parse not needed
This commit is contained in:
parent
a8f2eb666b
commit
885aaadd4f
376
src/parse.ts
376
src/parse.ts
|
@ -1,376 +0,0 @@
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { objAsString } from "./objAsString.js";
|
|
||||||
import { Err } from "./types.js";
|
|
||||||
|
|
||||||
// json_parse extracted from the json-bigint npm library
|
|
||||||
// regexpxs extracted from
|
|
||||||
// (c) BSD-3-Clause
|
|
||||||
// https://github.com/fastify/secure-json-parse/graphs/contributors and https://github.com/hapijs/bourne/graphs/contributors
|
|
||||||
const suspectProtoRx =
|
|
||||||
/(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])/;
|
|
||||||
const suspectConstructorRx =
|
|
||||||
/(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)/;
|
|
||||||
let json_parse = function (options) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// This is a function that can parse a JSON text, producing a JavaScript
|
|
||||||
// data structure. It is a simple, recursive descent parser. It does not use
|
|
||||||
// eval or regular expressions, so it can be used as a model for implementing
|
|
||||||
// a JSON parser in other languages.
|
|
||||||
|
|
||||||
// We are defining the function inside of another function to avoid creating
|
|
||||||
// global variables.
|
|
||||||
|
|
||||||
// Default options one can override by passing options to the parse()
|
|
||||||
let _options = {
|
|
||||||
strict: false, // not being strict means do not generate syntax errors for "duplicate key"
|
|
||||||
storeAsString: false, // toggles whether the values should be stored as BigNumber (default) or a string
|
|
||||||
alwaysParseAsBig: false, // toggles whether all numbers should be Big
|
|
||||||
protoAction: "error",
|
|
||||||
constructorAction: "error",
|
|
||||||
};
|
|
||||||
|
|
||||||
// If there are options, then use them to override the default _options
|
|
||||||
if (options !== undefined && options !== null) {
|
|
||||||
if (options.strict === true) {
|
|
||||||
_options.strict = true;
|
|
||||||
}
|
|
||||||
if (options.storeAsString === true) {
|
|
||||||
_options.storeAsString = true;
|
|
||||||
}
|
|
||||||
_options.alwaysParseAsBig = options.alwaysParseAsBig === true ? options.alwaysParseAsBig : false;
|
|
||||||
|
|
||||||
if (typeof options.constructorAction !== "undefined") {
|
|
||||||
if (
|
|
||||||
options.constructorAction === "error" ||
|
|
||||||
options.constructorAction === "ignore" ||
|
|
||||||
options.constructorAction === "preserve"
|
|
||||||
) {
|
|
||||||
_options.constructorAction = options.constructorAction;
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`Incorrect value for constructorAction option, must be "error", "ignore" or undefined but passed ${options.constructorAction}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof options.protoAction !== "undefined") {
|
|
||||||
if (options.protoAction === "error" || options.protoAction === "ignore" || options.protoAction === "preserve") {
|
|
||||||
_options.protoAction = options.protoAction;
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`Incorrect value for protoAction option, must be "error", "ignore" or undefined but passed ${options.protoAction}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let at, // The index of the current character
|
|
||||||
ch, // The current character
|
|
||||||
escapee = {
|
|
||||||
'"': '"',
|
|
||||||
"\\": "\\",
|
|
||||||
"/": "/",
|
|
||||||
b: "\b",
|
|
||||||
f: "\f",
|
|
||||||
n: "\n",
|
|
||||||
r: "\r",
|
|
||||||
t: "\t",
|
|
||||||
},
|
|
||||||
text,
|
|
||||||
error = function (m) {
|
|
||||||
// Call error when something is wrong.
|
|
||||||
|
|
||||||
throw {
|
|
||||||
name: "SyntaxError",
|
|
||||||
message: m,
|
|
||||||
at: at,
|
|
||||||
text: text,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
next = function (c) {
|
|
||||||
// If a c parameter is provided, verify that it matches the current character.
|
|
||||||
|
|
||||||
if (c && c !== ch) {
|
|
||||||
error("Expected '" + c + "' instead of '" + ch + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the next character. When there are no more characters,
|
|
||||||
// return the empty string.
|
|
||||||
|
|
||||||
ch = text.charAt(at);
|
|
||||||
at += 1;
|
|
||||||
return ch;
|
|
||||||
},
|
|
||||||
number = function () {
|
|
||||||
// Parse a number value.
|
|
||||||
|
|
||||||
let number,
|
|
||||||
string = "";
|
|
||||||
|
|
||||||
if (ch === "-") {
|
|
||||||
string = "-";
|
|
||||||
next("-");
|
|
||||||
}
|
|
||||||
while (ch >= "0" && ch <= "9") {
|
|
||||||
string += ch;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
if (ch === ".") {
|
|
||||||
string += ".";
|
|
||||||
while (next() && ch >= "0" && ch <= "9") {
|
|
||||||
string += ch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ch === "e" || ch === "E") {
|
|
||||||
string += ch;
|
|
||||||
next();
|
|
||||||
if (ch === "-" || ch === "+") {
|
|
||||||
string += ch;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
while (ch >= "0" && ch <= "9") {
|
|
||||||
string += ch;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
number = +string;
|
|
||||||
if (!isFinite(number)) {
|
|
||||||
error("Bad number");
|
|
||||||
} else {
|
|
||||||
if (Number.isSafeInteger(number)) return !_options.alwaysParseAsBig ? number : BigInt(number);
|
|
||||||
// Number with fractional part should be treated as number(double) including big integers in scientific notation, i.e 1.79e+308
|
|
||||||
else return _options.storeAsString ? string : /[.eE]/.test(string) ? number : BigInt(string);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
string = function () {
|
|
||||||
// Parse a string value.
|
|
||||||
|
|
||||||
let hex,
|
|
||||||
i,
|
|
||||||
string = "",
|
|
||||||
uffff;
|
|
||||||
|
|
||||||
// When parsing for string values, we must look for " and \ characters.
|
|
||||||
|
|
||||||
if (ch === '"') {
|
|
||||||
let startAt = at;
|
|
||||||
while (next()) {
|
|
||||||
if (ch === '"') {
|
|
||||||
if (at - 1 > startAt) string += text.substring(startAt, at - 1);
|
|
||||||
next();
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
if (ch === "\\") {
|
|
||||||
if (at - 1 > startAt) string += text.substring(startAt, at - 1);
|
|
||||||
next();
|
|
||||||
if (ch === "u") {
|
|
||||||
uffff = 0;
|
|
||||||
for (i = 0; i < 4; i += 1) {
|
|
||||||
hex = parseInt(next(), 16);
|
|
||||||
if (!isFinite(hex)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
uffff = uffff * 16 + hex;
|
|
||||||
}
|
|
||||||
string += String.fromCharCode(uffff);
|
|
||||||
} else if (typeof escapee[ch] === "string") {
|
|
||||||
string += escapee[ch];
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
startAt = at;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error("Bad string");
|
|
||||||
},
|
|
||||||
white = function () {
|
|
||||||
// Skip whitespace.
|
|
||||||
|
|
||||||
while (ch && ch <= " ") {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
word = function () {
|
|
||||||
// true, false, or null.
|
|
||||||
|
|
||||||
switch (ch) {
|
|
||||||
case "t":
|
|
||||||
next("t");
|
|
||||||
next("r");
|
|
||||||
next("u");
|
|
||||||
next("e");
|
|
||||||
return true;
|
|
||||||
case "f":
|
|
||||||
next("f");
|
|
||||||
next("a");
|
|
||||||
next("l");
|
|
||||||
next("s");
|
|
||||||
next("e");
|
|
||||||
return false;
|
|
||||||
case "n":
|
|
||||||
next("n");
|
|
||||||
next("u");
|
|
||||||
next("l");
|
|
||||||
next("l");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
error("Unexpected '" + ch + "'");
|
|
||||||
},
|
|
||||||
value, // Place holder for the value function.
|
|
||||||
array = function () {
|
|
||||||
// Parse an array value.
|
|
||||||
|
|
||||||
let array = [];
|
|
||||||
|
|
||||||
if (ch === "[") {
|
|
||||||
next("[");
|
|
||||||
white();
|
|
||||||
if (ch === "]") {
|
|
||||||
next("]");
|
|
||||||
return array; // empty array
|
|
||||||
}
|
|
||||||
while (ch) {
|
|
||||||
array.push(value());
|
|
||||||
white();
|
|
||||||
if (ch === "]") {
|
|
||||||
next("]");
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
next(",");
|
|
||||||
white();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error("Bad array");
|
|
||||||
},
|
|
||||||
object = function () {
|
|
||||||
// Parse an object value.
|
|
||||||
|
|
||||||
let key,
|
|
||||||
object = Object.create(null);
|
|
||||||
|
|
||||||
if (ch === "{") {
|
|
||||||
next("{");
|
|
||||||
white();
|
|
||||||
if (ch === "}") {
|
|
||||||
next("}");
|
|
||||||
return object; // empty object
|
|
||||||
}
|
|
||||||
while (ch) {
|
|
||||||
key = string();
|
|
||||||
white();
|
|
||||||
next(":");
|
|
||||||
if (_options.strict === true && Object.hasOwnProperty.call(object, key)) {
|
|
||||||
error('Duplicate key "' + key + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suspectProtoRx.test(key) === true) {
|
|
||||||
if (_options.protoAction === "error") {
|
|
||||||
error("Object contains forbidden prototype property");
|
|
||||||
} else if (_options.protoAction === "ignore") {
|
|
||||||
value();
|
|
||||||
} else {
|
|
||||||
object[key] = value();
|
|
||||||
}
|
|
||||||
} else if (suspectConstructorRx.test(key) === true) {
|
|
||||||
if (_options.constructorAction === "error") {
|
|
||||||
error("Object contains forbidden constructor property");
|
|
||||||
} else if (_options.constructorAction === "ignore") {
|
|
||||||
value();
|
|
||||||
} else {
|
|
||||||
object[key] = value();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
object[key] = value();
|
|
||||||
}
|
|
||||||
|
|
||||||
white();
|
|
||||||
if (ch === "}") {
|
|
||||||
next("}");
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
next(",");
|
|
||||||
white();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error("Bad object");
|
|
||||||
};
|
|
||||||
|
|
||||||
value = function () {
|
|
||||||
// Parse a JSON value. It could be an object, an array, a string, a number,
|
|
||||||
// or a word.
|
|
||||||
|
|
||||||
white();
|
|
||||||
switch (ch) {
|
|
||||||
case "{":
|
|
||||||
return object();
|
|
||||||
case "[":
|
|
||||||
return array();
|
|
||||||
case '"':
|
|
||||||
return string();
|
|
||||||
case "-":
|
|
||||||
return number();
|
|
||||||
default:
|
|
||||||
return ch >= "0" && ch <= "9" ? number() : word();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the json_parse function. It will have access to all of the above
|
|
||||||
// functions and variables.
|
|
||||||
|
|
||||||
return function (source, reviver) {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
text = source + "";
|
|
||||||
at = 0;
|
|
||||||
ch = " ";
|
|
||||||
result = value();
|
|
||||||
white();
|
|
||||||
if (ch) {
|
|
||||||
error("Syntax error");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a reviver function, we recursively walk the new structure,
|
|
||||||
// passing each name/value pair to the reviver function for possible
|
|
||||||
// transformation, starting with a temporary root object that holds the result
|
|
||||||
// in an empty key. If there is not a reviver function, we simply return the
|
|
||||||
// result.
|
|
||||||
|
|
||||||
return typeof reviver === "function"
|
|
||||||
? (function walk(holder, key) {
|
|
||||||
let v,
|
|
||||||
value = holder[key];
|
|
||||||
if (value && typeof value === "object") {
|
|
||||||
Object.keys(value).forEach(function (k) {
|
|
||||||
v = walk(value, k);
|
|
||||||
if (v !== undefined) {
|
|
||||||
value[k] = v;
|
|
||||||
} else {
|
|
||||||
delete value[k];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return reviver.call(holder, key, value);
|
|
||||||
})({ "": result }, "")
|
|
||||||
: result;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// parseJSON is a wrapper for JSONbig.parse that returns an error rather than
|
|
||||||
// throwing an error. JSONbig is an alternative to JSON.parse that decodes
|
|
||||||
// every number as a bigint. This is required when working with the skyd API
|
|
||||||
// because the skyd API uses 64 bit precision for all of its numbers, and
|
|
||||||
// therefore cannot be parsed losslessly by javascript. The skyd API is
|
|
||||||
// cryptographic, therefore full precision is required.
|
|
||||||
function parseJSON(json: string): [any, Err] {
|
|
||||||
try {
|
|
||||||
let obj = json_parse({ alwaysParseAsBig: true })(json);
|
|
||||||
return [obj, null];
|
|
||||||
} catch (err: any) {
|
|
||||||
return [{}, objAsString(err)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { parseJSON };
|
|
Loading…
Reference in New Issue