UNPKG

json-with-bigint

Version:

JS library that allows you to easily serialize and deserialize data with BigInt values

111 lines (84 loc) 4.18 kB
const noiseValue = /^-?\d+n+$/; // Noise - strings that match the custom format before being converted to it const originalStringify = JSON.stringify; const originalParse = JSON.parse; /* Function to serialize value to a JSON string. Converts BigInt values to a custom format (strings with digits and "n" at the end) and then converts them to proper big integers in a JSON string. */ const JSONStringify = (value, replacer, space) => { if ("rawJSON" in JSON) { return originalStringify( value, (key, value) => { if (typeof value === "bigint") return JSON.rawJSON(value.toString()); if (typeof replacer === "function") return replacer(key, value); if (Array.isArray(replacer) && replacer.includes(key)) return value; return value; }, space ); } if (!value) return originalStringify(value, replacer, space); const bigInts = /([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g; const noise = /([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g; const convertedToCustomJSON = originalStringify( value, (key, value) => { const isNoise = typeof value === "string" && Boolean(value.match(noiseValue)); if (isNoise) return value.toString() + "n"; // Mark noise values with additional "n" to offset the deletion of one "n" during the processing if (typeof value === "bigint") return value.toString() + "n"; if (typeof replacer === "function") return replacer(key, value); if (Array.isArray(replacer) && replacer.includes(key)) return value; return value; }, space ); const processedJSON = convertedToCustomJSON.replace(bigInts, "$1$2$3"); // Delete one "n" off the end of every BigInt value const denoisedJSON = processedJSON.replace(noise, "$1$2$3"); // Remove one "n" off the end of every noisy string return denoisedJSON; }; /* Function to parse JSON. If JSON has number values greater than Number.MAX_SAFE_INTEGER, we convert those values to a custom format, then parse them to BigInt values. Other types of values are not affected and parsed as native JSON.parse() would parse them. */ const JSONParse = (text, reviver) => { if (!text) return originalParse(text, reviver); const MAX_INT = Number.MAX_SAFE_INTEGER.toString(); const MAX_DIGITS = MAX_INT.length; const stringsOrLargeNumbers = /"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g; const noiseValueWithQuotes = /^"-?\d+n+"$/; // Noise - strings that match the custom format before being converted to it const customFormat = /^-?\d+n$/; // Find and mark big numbers with "n" const serializedData = text.replace( stringsOrLargeNumbers, (text, digits, fractional, exponential) => { const isString = text[0] === '"'; const isNoise = isString && Boolean(text.match(noiseValueWithQuotes)); if (isNoise) return text.substring(0, text.length - 1) + 'n"'; // Mark noise values with additional "n" to offset the deletion of one "n" during the processing const isFractionalOrExponential = fractional || exponential; const isLessThanMaxSafeInt = digits && (digits.length < MAX_DIGITS || (digits.length === MAX_DIGITS && digits <= MAX_INT)); // With a fixed number of digits, we can correctly use lexicographical comparison to do a numeric comparison if (isString || isFractionalOrExponential || isLessThanMaxSafeInt) return text; return '"' + text + 'n"'; } ); // Convert marked big numbers to BigInt return originalParse(serializedData, (key, value, context) => { const isCustomFormatBigInt = typeof value === "string" && Boolean(value.match(customFormat)); if (isCustomFormatBigInt) return BigInt(value.substring(0, value.length - 1)); const isNoiseValue = typeof value === "string" && Boolean(value.match(noiseValue)); if (isNoiseValue) return value.substring(0, value.length - 1); // Remove one "n" off the end of the noisy string if (typeof reviver !== "function") return value; return reviver(key, value, context); }); }; module.exports = { JSONStringify, JSONParse };