As of this writing, JavaScript's JSON.parse cannot serialize the new JavaScript type BigInt.
Imagine you have the following:
const data = {
value1: BigInt('1231231231231231213'),
deep: {
// NOTE the "n" at the end -- also a BigInt!
value2: 848484848484848484884n,
}
}
If you try to just JSON.stringify(data) you will get the error TypeError: Do not know how to serialize a BigInt.
Serialization and Deserialization
It should be noted that how you choose to serialize your BigInts affects how you deserialize your BigInts. Generally, I serialize them by doing appending the "n" suffix to the end, similar to how we can declare a BigInt inline. (BigInt(0) and 0n yield the same result).
Serialization
Here we use JSON.stringify's second argument (It's not always null!!! haha.) which is the replacer. The job of this function, if provided, is to determine how to serialize something based off of it's key and value. If the typeof the value is "bigint", we're going to convert it to a string, and tack an "n" to the end.
// Serialization
const json = JSON.stringify(data, (key, value) =>
typeof value === "bigint" ? value.toString() + "n" : value
);
The result: json is:
{
"value1": "1231231231231231213n",
"deep": {
"value2": "848484848484848484884n",
}
}
Deserialization
In order to deserialize what we have above, we can use the second argument to JSON.parse(). (I bet most people didn't know it has a second argument) This is called the reviver, and it's job is to do basically the opposite of the replacer above.
Here we'll test for the type and shape of the value to see that it matches a bunch of numbers followed by an "n".
// Deserialize
const backAgain = JSON.parse(json, (key, value) => {
if (typeof value === "string" && /^\d+n$/.test(value)) {
return BigInt(value.substr(0, value.length - 1));
}
return value;
});
Alternative serializations
This is all a little tricky, because you have to be sure that none of your other data is in a format where it's a bunch of numbers and an "n" at the end. If it is, you need to change your serialization strategy. For example, perhaps you serialize to BigInt::1231232123 and deserialize the same at the other side, such as the example below:
// Serialize
const json = JSON.stringify(data, (key, value) =>
typeof value === "bigint" ? `BIGINT::${value}` : value
);
// Deserialize
const backAgain = JSON.parse(json, (key, value) => {
if (typeof value === "string" && value.startsWith('BIGINT::')) {
return BigInt(value.substr(8));
}
return value;
});
The choice is really up to you, just as long as you have the tools to do it.
Top comments (3)
Interesting post, thanks, and it'd be great to have a common convention for all native constructors that can't be serialized, and these two helpers should already work for most cases (missing recursive parse/stringify for special cases such as Map or Set).
Testable via:
In CircularJSON, differently from flatted, I used the
~special char to signal recursion, but having a prefix such as\x01, or any other non common chars sequences, might be all it takes to have more portable data structures that can be resumed.Very cool, TIL that parse takes a second argument!
In the serialization and deserialization snippets, looks like there's a reference to
vwhich is undefined - looks like it got renamed tovalue?AH! Typo! thanks. :)