A JSON.rawJSON and JSON.isRawJSON ponyfill (meaning: no global patch applied).
import * as JSON from '@ungap/raw-json';
JSON.stringify({
a: JSON.rawJSON('12345678901234567890'),
b: JSON.rawJSON(12345678901234567890n),
c: JSON.rawJSON('null'),
d: JSON.rawJSON(null),
e: JSON.rawJSON(123),
f: JSON.rawJSON('"hello raw JSON"'),
g: JSON.rawJSON(true),
h: JSON.rawJSON(false)
});
JSON.parse(
JSON.stringify(
JSON.rawJSON('12345678901234567890')
),
// this is an extra utility to have BigInts back
JSON.reviver
);
// 12345678901234567890n
Practically born to solve the fact serialized BigInts are broken in current JS world, but not necessarily in other programming languages such as Python, this ponyfill allows its consumers to safely store, or retrieve, also BigInt primitives as valid values.
const myBigInt = 12345678901234567890n;
// ⚠️ Uncaught TypeError: Do not know how to serialize a BigInt
JSON.stringify({ myBigInt });
// parsing too big numbers is still allowed by JSON specs
JSON.parse('{"myBigInt":12345678901234567890}');
// ⚠️ but the result is lost or incorrect
{ myBigInt: 12345678901234567000 }
// ^^^
Enter JSON.rawJSON
, an utility that accepts only primitives, except Symbols, and returns a very special, frozen, null
prototyped object that contains a rawJSON
public field.
import * as JSON from 'https://esm.run/@ungap/raw-json';
const myBigInt = 12345678901234567890n;
JSON.isRawJSON(myBigInt); // false
const asRawJSON = JSON.rawJSON(myBigInt);
// { rawJSON: '12345678901234567890' }
JSON.isRawJSON(asRawJSON); // true
JSON.stringify({ myBigInt: asRawJSON });
// {"myBigInt":12345678901234567890}
At this point the JSON string {"myBigInt":12345678901234567890}
can be posted or parsed back, but to retrieve a BigInt back we need to use a reviver function.
Differently from the pre-rawJSON era, a reviver would receive a key
and a value
as arguments, but not the recently introduced context
.
Such context
is passed only if the value
is a primitive JSON value, meaning it’s not present when such value
is an object or an array, and it contains a source
field pointing at the original representation of such value
as string.
JSON.parse(
'{"myBigInt":12345678901234567890}',
(key, value, context) => {
if (context && typeof value === 'number') {
console.log({ value, source: context.source });
// { value: 12345678901234567000, source: '12345678901234567890' }
return BigInt(context.source);
}
return value;
}
);
{ myBigInt: 12345678901234567890n }
To simplify the repeated reviver dance, this module also offers a non standard JSON.reviver
helper that will recognize BigInt and return these when found in the source, keeping all other numbers the same.
This module goal is to temporarily enable rawJSON until it gets native support and it moves the minimum amount of code to do so to keep it simple, size-friendly, yet still performant.
If you are interested in code-size and performance you can test yourself in Safari or Firefox.
On average, this module is ~1.5X faster on simple cases and 3X up to 5X faster on more complex (nested) cases.
Compared to native Chrome/ium implementation, this module is nearly as fast as that one too, specially with complex values (same test page, this time with Chrome/ium).