Skip to content

Instantly share code, notes, and snippets.

@ValeryVerkhoturov
Last active September 22, 2025 05:39
Show Gist options
  • Save ValeryVerkhoturov/96bbd8a83f2e9be4dd1c997cd2106cb1 to your computer and use it in GitHub Desktop.
Save ValeryVerkhoturov/96bbd8a83f2e9be4dd1c997cd2106cb1 to your computer and use it in GitHub Desktop.
Handle BigInt in JSON serialization & deserialization
const WBNBReportsJSON = (() => {
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 stringify = (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 parse = (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);
});
};
return {
...JSON,
stringify,
parse
}
})()
const input = `{"num":1, "bigInt":25002513820250928}`;
const testJsonObj = (jsonObj) => {
const parsed = jsonObj.parse(input);
console.log("parsed.num:", parsed.num, `(type: ${typeof parsed.num})`);
console.log("parsed.bigInt:", parsed.bigInt.toString(), `(type: ${typeof parsed.bigInt})`);
const serialized = jsonObj.stringify(parsed);
console.log(serialized);
};
console.log("JSON:");
testJsonObj(JSON);
console.log("WBNBReportsJSON:");
testJsonObj(WBNBReportsJSON);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment