summaryrefslogtreecommitdiff
path: root/src/content/tils/2021/08/11/js-bigint-reviver.adoc
blob: f7dd3de5982c12991ca16492fe03e4f533ea4c30 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
= Encoding and decoding JavaScript BigInt values with reviver
:updatedat: 2021-08-13

:reviver-fn: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#using_the_reviver_parameter
:bigint: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
:json-rfc: https://datatracker.ietf.org/doc/html/rfc8259

`JSON.parse()` accepts a second parameter: a {reviver-fn}[`reviver()` function].
It is a function that can be used to transform the `JSON` values as they're
being parsed.

As it turns out, when combined with JavaScript's {bigint}[`BigInt`] type, you
can parse and encode JavaScript `BigInt` numbers via JSON:

[source,javascript]
----
const bigIntReviver = (_, value) =>
    typeof value === "string" && value.match(/^-?[0-9]+n$/)
        ? BigInt(value.slice(0, value.length - 1))
        : value;
----

I chose to interpret strings that contains only numbers and an ending `n`
suffix as `BigInt` values, similar to how JavaScript interprets `123` (a number)
differently from `123n` (a `bigint`);

We do those checks before constructing the `BigInt` to avoid throwing needless
exceptions and catching them on the parsing function, as this could easily
become a bottleneck when parsing large JSON values.

In order to do the full roundtrip, we now only need the `toJSON()` counterpart:

[source,javascript]
----
BigInt.prototype.toJSON = function() {
    return this.toString() + "n";
};
----

With both `bigIntReviver` and `toJSON` defined, we can now successfully parse
and encode JavaScript objects with `BigInt` values transparently:

[source,javascript]
----
const s = `[
    null,
    true,
    false,
    -1,
    3.14,
    "a string",
    { "a-number": "-123" },
    { "a-bigint": "-123n" }
]`;

const parsed = JSON.parse(s, bigIntReviver);
const s2 = JSON.stringify(parsed);

console.log(parsed);
console.log(s2);

console.log(typeof parsed[6]["a-number"])
console.log(typeof parsed[7]["a-bigint"])
----

The output of the above is:

....
[
  null,
  true,
  false,
  -1,
  3.14,
  'a string',
  { 'a-number': '-123' },
  { 'a-bigint': -123n }
]
[null,true,false,-1,3.14,"a string",{"a-number":"-123"},{"a-bigint":"-123n"}]
string
bigint
....

If you're on a web browser, you can probably try copying and pasting the above
code on the console right now, as is.

Even though {json-rfc}[`JSON`] doesn't include `BigInt` number, encoding and
decoding them as strings is quite trivial on JavaScript.