mihebo
mihebo

Reputation: 115

How to make JSON.parse() to treat all the Numbers as BigInt?

I have some numbers in json which overflow the Number type, so I want it to be bigint, but how?

{"foo":[[0],[64],[89],[97]],"bar":[[2323866757078990912,144636906343245838,441695983932742154,163402272522524744],[2477006750808014916,78818525534420994],[18577623609266200],[9008333127155712]]}

Upvotes: 8

Views: 22034

Answers (2)

Ivan Korolenko
Ivan Korolenko

Reputation: 31

You can try my library that solves this problem: https://www.npmjs.com/package/json-with-bigint

Example:

import { JSONParse } from 'json-with-bigint';
    
const yourJSON = `{"someValue":42,"someBigValue":10000000000000000365}`;

JSONParse(yourJSON); // { someValue: 42, someBigValue: 10000000000000000365n }

Library will automatically figure out what values are BigInt, so no need to pass a list of specific keys with BigInt values.

It also supports consistent round-trip operations (parse - stringify - parse). Deserialized values will be the same as the ones you serialized initially and vice versa.

import { JSONParse, JSONStringify } from 'json-with-bigint';
    
const yourJSON = `{"someValue":42,"someBigValue":10000000000000000365}`;

JSONParse(yourJSON); // { someValue: 42, someBigValue: 10000000000000000365n }
JSONStringify(data); // '{"someValue":42,"someBigValue":10000000000000000365}'
JSONParse(JSONStringify(data)); // { someValue: 42, someBigValue: 10000000000000000365n }

Upvotes: 3

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

TLDR;

You may employ JSON.parse() reviver parameter

Detailed Solution

To control JSON.parse() behavior that way, you can make use of the second parameter of JSON.parse (reviver) - the function that pre-processes key-value pairs (and may potentially pass desired values to BigInt()).

Yet, the values recognized as numbers will still be coerced (the credit for pinpointing this issue goes to @YohanesGultom).

To get around this, you may enquote your big numbers (to turn them into strings) in your source JSON string, so that their values are preserved upon converting to bigint.

As long as you wish to convert to bigint only certain numbers, you would need to pick up appropriate criteria (e.g. to check whether the value exceeds Number.MAX_SAFE_INTEGER with Number.isSafeInteger(), as @PeterSeliger has suggested).

Thus, your problem may be solved with something, like this:

// source JSON string

const input = `{"foo":[[0],[64],[89],[97]],"bar":[[2323866757078990912,144636906343245838,441695983932742154,163402272522524744],[2477006750808014916,78818525534420994],[18577623609266200],[9008333127155712]]}`


// function that implements desired criteria
// to separate *big numbers* from *small* ones
//
// (works for input parameter num of type number/string)

const isBigNumber = num => !Number.isSafeInteger(+num)


// function that enquotes *big numbers* matching
// desired criteria into double quotes inside
// JSON string
//
// (function checking for *big numbers* may be
// passed as a second parameter for flexibility)

const enquoteBigNumber = (jsonString, bigNumChecker) =>
    jsonString
        .replaceAll(
            /([:\s\[,]*)(\d+)([\s,\]]*)/g,
            (matchingSubstr, prefix, bigNum, suffix) =>
                bigNumChecker(bigNum)
                    ? `${prefix}"${bigNum}"${suffix}`
                    : matchingSubstr
        )


// parser that turns matching *big numbers* in
// source JSON string to bigint

const parseWithBigInt = (jsonString, bigNumChecker) =>
    JSON.parse(
        enquoteBigNumber(jsonString, bigNumChecker),
        (key, value) =>
            !isNaN(value) && bigNumChecker(value)
                ? BigInt(value)
                : value
    )

// resulting output

const output = parseWithBigInt(input, isBigNumber)


console.log("output.foo[1][0]: \n", output.foo[1][0], `(type: ${typeof output.foo[1][0]})`)
console.log("output.bar[0][0]: \n", output.bar[0][0].toString(), `(type: ${typeof output.bar[0][0]})`)
.as-console-wrapper{min-height: 100% !important;}

Note: you may find RegExp pattern to match strings of digits among JSON values not quite robust, so feel free to come up with yours (as mine was the quickest I managed to pick off the top of my head for demo purposes)

Note: you may still opt in for some library, as it was suggested by @YohanesGultom, yet adding 10k to your client bundle or 37k to your server-side dependencies (possibly, to docker image size) for that sole purpose may not be quite reasonable.

Upvotes: 17

Related Questions