6

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]]}
Yevhen Horbunkov
  • 14,965
  • 3
  • 20
  • 42
mihebo
  • 75
  • 1
  • 5
  • 1
    Probably there's answer available here: [node.js - Is there any proper way to parse JSON with large numbers? (long, bigint, int64)](https://stackoverflow.com/questions/18755125/node-js-is-there-any-proper-way-to-parse-json-with-large-numbers-long-bigin) – koloml Oct 20 '21 at 10:34
  • SAD. I'd like to get some build-in solution... – mihebo Oct 20 '21 at 10:37
  • I suggest to use strings and then convert them into `BigInt` numbers. Like some REST APIs doing (like Discord API with BigInt IDs). It's much easier and you event can convert such variables to `BigInt` using built-in converter. – koloml Oct 20 '21 at 10:39

2 Answers2

11

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.

Yevhen Horbunkov
  • 14,965
  • 3
  • 20
  • 42
  • 2
    A test could be based too on ... [`Number.isSafeInteger`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger) – Peter Seliger Oct 20 '21 at 11:01
  • Please be aware that this **doesn't preserve** the number value. If you add one more line `console.log(output.bar[0][0].toString())` you will see `2323866757078990848`. Consider custom parsing or library like https://www.npmjs.com/package/json-bigint – Yohanes Gultom Aug 04 '22 at 01:38
  • 1
    Another limitation of this solution is that string JSON values that contain only digits are also going to be converted to BigInt, whereas they were probably intended to remain as strings. – DanielM Sep 12 '22 at 12:04
0

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 }
  • 1
    Sorry, `{ "foo": 123n }` is not a valid JSON. You are defining a new language. – kien_coi_1997 May 15 '23 at 02:19
  • 1
    **Fixed in version 2.0.0** It's an internal format used to identify values that should be parsed as BigInt. Without it a value like `26n` would be converted to `26`. Which is fine when you want to send JSON to back end, but can lead to type errors if you use JSON as a localStorage value in front end. But you're right, since this library is called `JSON-with-bigint`, it should generate valid JSON. I made changes to the code. Now it will always generate a valid JSON. Compatibility with strings generated by `JSONStringify()` in previous versions of the lib is also preserved. – Ivan Korolenko May 18 '23 at 00:52