From b2637f9e03d14cc8f335bb4b090da9f8f3410a14 Mon Sep 17 00:00:00 2001 From: Quoc-Hao TRAN Date: Thu, 9 Jul 2020 10:30:21 -0400 Subject: [PATCH 1/5] use native BigInt as option, add always option --- README.md | 25 +++++ lib/parse.js | 11 ++- lib/stringify.js | 1 + package-lock.json | 167 ++++++++++++++++++++++++++++++++++ package.json | 4 +- test/bigint-parse-test.js | 59 ++++++++++++ test/bigint-stringify-test.js | 36 ++++++++ test/bigint-test.js | 2 +- 8 files changed, 299 insertions(+), 6 deletions(-) create mode 100644 package-lock.json create mode 100644 test/bigint-parse-test.js create mode 100644 test/bigint-stringify-test.js diff --git a/README.md b/README.md index 7cb7919..777cd08 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,13 @@ Default type: object, With option type: string ``` +#### options.alwaysBigNumber, boolean, default false +Specifies if all numbers should be stored as BigNumber/BigInt. + +Note that this is a dangerous behavior as it breaks the default functionality of being able to convert back-and-forth without data type changes (as this will convert all Number to be-and-stay BigNumber/BigInt) + +#### options.useBigInt, boolean, default false +Specifies if parser uses native BigInt instead of bignumber.js ### Links: - [RFC4627: The application/json Media Type for JavaScript Object Notation (JSON)](http://www.ietf.org/rfc/rfc4627.txt) @@ -115,3 +122,21 @@ Default type: object, With option type: string - [What is JavaScript's Max Int? What's the highest Integer value a Number can go to without losing precision?](http://stackoverflow.com/questions/307179/what-is-javascripts-max-int-whats-the-highest-integer-value-a-number-can-go-t) - [Large numbers erroneously rounded in Javascript](http://stackoverflow.com/questions/1379934/large-numbers-erroneously-rounded-in-javascript) +### Native BigInt support + +#### Stringifying +Full support out-of-the-box, stringifies BigInts as pure numbers (no quotes, no `n`). + +#### Parsing +```js +var JSONbigString = require('json-bigint')({"useBigInt": true}); +``` +If you want to force all numbers to be parsed as `bignumber.js` +```js +var JSONbigString = require('json-bigint')({"alwaysBigNumber": true}); +``` +If you want to force all numbers to be parsed as `BigInt`s +(you probably do! Otherwise any calulations become a real headache): +```js +var JSONbigString = require('json-bigint')({"alwaysBigNumber": true, "useBigInt": true}); +``` \ No newline at end of file diff --git a/lib/parse.js b/lib/parse.js index 2941c81..1f9adbe 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -76,7 +76,9 @@ var json_parse = function (options) { // Default options one can override by passing options to the parse() var _options = { "strict": false, // not being strict means do not generate syntax errors for "duplicate key" - "storeAsString": false // toggles whether the values should be stored as BigNumber (default) or a string + "storeAsString": false, // toggles whether the values should be stored as BigNumber (default) or a string + "alwaysBigNumber": false, // toggles whether all numbers should be BigNumber + "useBigInt": false // toggles whether to use native BigInt instead of bignumber.js }; @@ -88,6 +90,8 @@ var json_parse = function (options) { if (options.storeAsString === true) { _options.storeAsString = true; } + _options.alwaysBigNumber = options.alwaysBigNumber === true ? options.alwaysBigNumber : false + _options.useBigInt = options.useBigInt === true ? options.useBigInt : false } @@ -174,8 +178,9 @@ var json_parse = function (options) { //if (number > 9007199254740992 || number < -9007199254740992) // Bignumber has stricter check: everything with length > 15 digits disallowed if (string.length > 15) - return (_options.storeAsString === true) ? string : new BigNumber(string); - return number; + return _options.storeAsString ? string : _options.useBigInt ? BigInt(string) : new BigNumber(string); + else + return !_options.alwaysBigNumber ? number : _options.useBigInt ? BigInt(number) : new BigNumber(number); } }, diff --git a/lib/stringify.js b/lib/stringify.js index ce0592e..3bd5269 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -249,6 +249,7 @@ var JSON = module.exports; case 'boolean': case 'null': + case 'bigint': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ae66fd5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,167 @@ +{ + "name": "json-bigint", + "version": "0.3.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "assertion-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true + }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "chai": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-1.9.2.tgz", + "integrity": "sha1-Pxog+CsLnXQ3V30k1vErGmnTtZA=", + "dev": true, + "requires": { + "assertion-error": "1.0.0", + "deep-eql": "0.1.3" + } + }, + "commander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + } + }, + "diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=", + "dev": true + }, + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "requires": { + "graceful-fs": "~2.0.0", + "inherits": "2", + "minimatch": "~0.2.11" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + }, + "growl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + }, + "mocha": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz", + "integrity": "sha1-80ODLZ/gx9l8ZPxwRI9RNt+f7Vs=", + "dev": true, + "requires": { + "commander": "2.0.0", + "debug": "*", + "diff": "1.0.7", + "glob": "3.2.3", + "growl": "1.7.x", + "jade": "0.26.3", + "mkdirp": "0.3.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } +} diff --git a/package.json b/package.json index 70cdae3..6b0f5d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-bigint", - "version": "0.3.0", + "version": "0.4.0", "description": "JSON.parse with bigints support", "main": "index.js", "scripts": { @@ -26,4 +26,4 @@ "chai": "~1.9.1", "mocha": "~1.20.1" } -} +} \ No newline at end of file diff --git a/test/bigint-parse-test.js b/test/bigint-parse-test.js new file mode 100644 index 0000000..47aaa65 --- /dev/null +++ b/test/bigint-parse-test.js @@ -0,0 +1,59 @@ +var mocha = require('mocha') + , assert = require('chai').assert + , expect = require('chai').expect + , BigNumber = require('bignumber.js') + ; + +describe("Testing native BigInt support: parse", function () { + if (typeof (BigInt) === 'undefined') { + console.log('No native BigInt'); + return; + } + var input = '{"big":92233720368547758070,"small":123}'; + + it("Should show JSONbig does support parsing native BigInt", function (done) { + var JSONbig = require('../index')({ + "useBigInt": true + }); + var obj = JSONbig.parse(input); + expect(obj.small, "small int").to.equal(123); + expect(obj.big.toString(), "big int").to.equal("92233720368547758070"); + expect(typeof obj.big, "big int").to.equal('bigint'); + done(); + }); + + it("Should show JSONbig does support forced parsing to native BigInt", function (done) { + var JSONbig = require('../index')({ + "alwaysBigNumber": true, + "useBigInt": true + }); + var obj = JSONbig.parse(input); + expect(obj.big.toString(), "big int").to.equal("92233720368547758070"); + expect(typeof obj.big, "big int").to.equal('bigint'); + expect(obj.small.toString(), "small int").to.equal("123"); + expect(typeof obj.small, "small int").to.equal('bigint'); + done(); + }); + + + it("Should show JSONbig does support native Bigint parse/stringify roundtrip", function (done) { + var JSONbig = require('../index')({ + "useBigInt": true + }); + var obj = JSONbig.parse(input); + var output = JSONbig.stringify(obj); + expect(output).to.equal(input); + done(); + }); + + it("Should show JSONbig does support native Bigint parse/stringify roundtrip when BigInt is forced", function (done) { + var JSONbig = require('../index')({ + "alwaysBigNumber": true, + "useBigInt": true + }); + var obj = JSONbig.parse(input); + var output = JSONbig.stringify(obj); + expect(output).to.equal(input); + done(); + }); +}); \ No newline at end of file diff --git a/test/bigint-stringify-test.js b/test/bigint-stringify-test.js new file mode 100644 index 0000000..bef4670 --- /dev/null +++ b/test/bigint-stringify-test.js @@ -0,0 +1,36 @@ +var mocha = require('mocha') + , assert = require('chai').assert + , expect = require('chai').expect + , BigNumber = require('bignumber.js') + ; + +describe("Testing native BigInt support: stringify", function () { + if (typeof (BigInt) === 'undefined') { + console.log('No native BigInt'); + return; + } + it("Should show JSONbig can stringify native BigInt", function (done) { + var JSONbig = require('../index'); + var obj = { + // We cannot use n-literals - otherwise older NodeJS versions fail on this test + big: eval("123456789012345678901234567890n"), + small: -42, + bigConstructed: BigInt(1), + smallConstructed: Number(2), + }; + expect(obj.small.toString(), "string from small int").to.equal("-42"); + expect(obj.big.toString(), "string from big int").to.equal("123456789012345678901234567890"); + expect(typeof obj.big, "typeof big int").to.equal('bigint'); + + var output = JSONbig.stringify(obj); + expect(output).to.equal( + '{' + + '"big":123456789012345678901234567890,' + + '"small":-42,' + + '"bigConstructed":1,' + + '"smallConstructed":2' + + '}' + ); + done(); + }); +}); \ No newline at end of file diff --git a/test/bigint-test.js b/test/bigint-test.js index 5f5e1a3..046aa5b 100644 --- a/test/bigint-test.js +++ b/test/bigint-test.js @@ -17,7 +17,7 @@ describe("Testing bigint support", function(){ done(); }); - it("Should show JSNbig does support bigint parse/stringify roundtrip", function(done){ + it("Should show JSONbig does support bigint parse/stringify roundtrip", function(done){ var JSONbig = require('../index'); var obj = JSONbig.parse(input); expect(obj.small.toString(), "string from small int").to.equal("123"); From fccddfd4e9ba6ba8ed138bb368e3d5436071491a Mon Sep 17 00:00:00 2001 From: Quoc-Hao TRAN Date: Thu, 9 Jul 2020 11:37:59 -0400 Subject: [PATCH 2/5] improve speed and memory usage, from #28 --- lib/parse.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 1f9adbe..62c5b25 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -196,12 +196,17 @@ var json_parse = function (options) { // When parsing for string values, we must look for " and \ characters. if (ch === '"') { + var startAt = at; while (next()) { if (ch === '"') { + if (at - 1 > startAt) + string += text.substring(startAt, at - 1); next(); return string; } if (ch === '\\') { + if (at - 1 > startAt) + string += text.substring(startAt, at - 1); next(); if (ch === 'u') { uffff = 0; @@ -218,8 +223,7 @@ var json_parse = function (options) { } else { break; } - } else { - string += ch; + startAt = at; } } } From 2446ea9f300f9b181fa30084aa90867a5ae80459 Mon Sep 17 00:00:00 2001 From: Quoc-Hao TRAN Date: Thu, 9 Jul 2020 22:31:44 -0400 Subject: [PATCH 3/5] change options name --- lib/parse.js | 12 ++++++------ test/bigint-parse-test.js | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 62c5b25..8229130 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -77,8 +77,8 @@ var json_parse = function (options) { var _options = { "strict": false, // not being strict means do not generate syntax errors for "duplicate key" "storeAsString": false, // toggles whether the values should be stored as BigNumber (default) or a string - "alwaysBigNumber": false, // toggles whether all numbers should be BigNumber - "useBigInt": false // toggles whether to use native BigInt instead of bignumber.js + "alwaysParseAsBig": false, // toggles whether all numbers should be Big + "useNativeBigInt": false // toggles whether to use native BigInt instead of bignumber.js }; @@ -90,8 +90,8 @@ var json_parse = function (options) { if (options.storeAsString === true) { _options.storeAsString = true; } - _options.alwaysBigNumber = options.alwaysBigNumber === true ? options.alwaysBigNumber : false - _options.useBigInt = options.useBigInt === true ? options.useBigInt : false + _options.alwaysParseAsBig = options.alwaysParseAsBig === true ? options.alwaysParseAsBig : false + _options.useNativeBigInt = options.useNativeBigInt === true ? options.useNativeBigInt : false } @@ -178,9 +178,9 @@ var json_parse = function (options) { //if (number > 9007199254740992 || number < -9007199254740992) // Bignumber has stricter check: everything with length > 15 digits disallowed if (string.length > 15) - return _options.storeAsString ? string : _options.useBigInt ? BigInt(string) : new BigNumber(string); + return _options.storeAsString ? string : _options.useNativeBigInt ? BigInt(string) : new BigNumber(string); else - return !_options.alwaysBigNumber ? number : _options.useBigInt ? BigInt(number) : new BigNumber(number); + return !_options.alwaysParseAsBig ? number : _options.useNativeBigInt ? BigInt(number) : new BigNumber(number); } }, diff --git a/test/bigint-parse-test.js b/test/bigint-parse-test.js index 47aaa65..e9755ff 100644 --- a/test/bigint-parse-test.js +++ b/test/bigint-parse-test.js @@ -13,7 +13,7 @@ describe("Testing native BigInt support: parse", function () { it("Should show JSONbig does support parsing native BigInt", function (done) { var JSONbig = require('../index')({ - "useBigInt": true + "useNativeBigInt": true }); var obj = JSONbig.parse(input); expect(obj.small, "small int").to.equal(123); @@ -24,8 +24,8 @@ describe("Testing native BigInt support: parse", function () { it("Should show JSONbig does support forced parsing to native BigInt", function (done) { var JSONbig = require('../index')({ - "alwaysBigNumber": true, - "useBigInt": true + "alwaysParseAsBig": true, + "useNativeBigInt": true }); var obj = JSONbig.parse(input); expect(obj.big.toString(), "big int").to.equal("92233720368547758070"); @@ -38,7 +38,7 @@ describe("Testing native BigInt support: parse", function () { it("Should show JSONbig does support native Bigint parse/stringify roundtrip", function (done) { var JSONbig = require('../index')({ - "useBigInt": true + "useNativeBigInt": true }); var obj = JSONbig.parse(input); var output = JSONbig.stringify(obj); @@ -48,8 +48,8 @@ describe("Testing native BigInt support: parse", function () { it("Should show JSONbig does support native Bigint parse/stringify roundtrip when BigInt is forced", function (done) { var JSONbig = require('../index')({ - "alwaysBigNumber": true, - "useBigInt": true + "alwaysParseAsBig": true, + "useNativeBigInt": true }); var obj = JSONbig.parse(input); var output = JSONbig.stringify(obj); From e2bf2b519d61a9d086c480a191848095bcba7b87 Mon Sep 17 00:00:00 2001 From: Quoc-Hao TRAN Date: Thu, 9 Jul 2020 22:42:20 -0400 Subject: [PATCH 4/5] update README, add Limitations section --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 777cd08..86378cb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ json-bigint [![Build Status](https://secure.travis-ci.org/sidorares/json-bigint.png)](http://travis-ci.org/sidorares/json-bigint) [![NPM](https://nodei.co/npm/json-bigint.png?downloads=true&stars=true)](https://nodei.co/npm/json-bigint/) -JSON.parse/stringify with bigints support. Based on Douglas Crockford [JSON.js](https://github.com/douglascrockford/JSON-js) package and [bignumber.js](https://github.com/MikeMcl/bignumber.js) library. +JSON.parse/stringify with bigints support. Based on Douglas Crockford [JSON.js](https://github.com/douglascrockford/JSON-js) package and [bignumber.js](https://github.com/MikeMcl/bignumber.js) library. Native Bigint was added to JS recently, so we added an option to leverage it. However, the parsing with native BigInt is kept an option for backward compability. While most JSON parsers assume numeric values have same precision restrictions as IEEE 754 double, JSON specification _does not_ say anything about number precision. Any floating point number in decimal (optionally scientific) notation is valid JSON value. It's a good idea to serialize values which might fall out of IEEE 754 integer precision as strings in your JSON api, but `{ "value" : 9223372036854775807}`, for example, is still a valid RFC4627 JSON string, and in most JS runtimes the result of `JSON.parse` is this object: `{ value: 9223372036854776000 }` @@ -129,14 +129,18 @@ Full support out-of-the-box, stringifies BigInts as pure numbers (no quotes, no #### Parsing ```js -var JSONbigString = require('json-bigint')({"useBigInt": true}); +var JSONbig = require('json-bigint')({"useNativeBigInt": true}); ``` If you want to force all numbers to be parsed as `bignumber.js` ```js -var JSONbigString = require('json-bigint')({"alwaysBigNumber": true}); +var JSONbig = require('json-bigint')({"alwaysParseAsBig": true}); ``` If you want to force all numbers to be parsed as `BigInt`s (you probably do! Otherwise any calulations become a real headache): ```js -var JSONbigString = require('json-bigint')({"alwaysBigNumber": true, "useBigInt": true}); -``` \ No newline at end of file +var JSONbig = require('json-bigint')({"alwaysParseAsBig": true, "useNativeBigInt": true}); +``` + +#### Limitations +Currently `s === JSONbig.stringify(JSONbig.parse(s))` but `o !== JSONbig.parse(JSONbig.stringify(o))` when `o` has value of something like `123n`, `JSONbig` stringify this as `123`, which becomes `number` when being reparsed. +There is currently no consistent way to deal with this issue, so we decided to leave it this way, handling this specific case is then up to users. \ No newline at end of file From 15565630b69dd684aa589b8e26a12f4b8e47307f Mon Sep 17 00:00:00 2001 From: Quoc-Hao TRAN Date: Fri, 10 Jul 2020 04:39:25 -0400 Subject: [PATCH 5/5] update README --- README.md | 91 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 86378cb..06d3dec 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ json-bigint [![Build Status](https://secure.travis-ci.org/sidorares/json-bigint.png)](http://travis-ci.org/sidorares/json-bigint) [![NPM](https://nodei.co/npm/json-bigint.png?downloads=true&stars=true)](https://nodei.co/npm/json-bigint/) -JSON.parse/stringify with bigints support. Based on Douglas Crockford [JSON.js](https://github.com/douglascrockford/JSON-js) package and [bignumber.js](https://github.com/MikeMcl/bignumber.js) library. Native Bigint was added to JS recently, so we added an option to leverage it. However, the parsing with native BigInt is kept an option for backward compability. +JSON.parse/stringify with bigints support. Based on Douglas Crockford [JSON.js](https://github.com/douglascrockford/JSON-js) package and [bignumber.js](https://github.com/MikeMcl/bignumber.js) library. + +Native `Bigint` was added to JS recently, so we added an option to leverage it instead of `bignumber.js`. However, the parsing with native `BigInt` is kept an option for backward compability. While most JSON parsers assume numeric values have same precision restrictions as IEEE 754 double, JSON specification _does not_ say anything about number precision. Any floating point number in decimal (optionally scientific) notation is valid JSON value. It's a good idea to serialize values which might fall out of IEEE 754 integer precision as strings in your JSON api, but `{ "value" : 9223372036854775807}`, for example, is still a valid RFC4627 JSON string, and in most JS runtimes the result of `JSON.parse` is this object: `{ value: 9223372036854776000 }` @@ -107,40 +109,83 @@ Default type: object, With option type: string ``` -#### options.alwaysBigNumber, boolean, default false -Specifies if all numbers should be stored as BigNumber/BigInt. +#### options.useNativeBigInt, boolean, default false +Specifies if parser uses native BigInt instead of bignumber.js -Note that this is a dangerous behavior as it breaks the default functionality of being able to convert back-and-forth without data type changes (as this will convert all Number to be-and-stay BigNumber/BigInt) +example: +```js +var JSONbig = require('json-bigint'); +var JSONbigNative = require('json-bigint')({"useNativeBigInt": true}); +var key = '{ "key": 993143214321423154315154321 }'; +console.log(`\n\nStoring the Number as native BigInt, instead of a BigNumber`); +console.log('Input:', key); +var normal = JSONbig.parse(key); +var nativeBigInt = JSONbigNative.parse(key); +console.log('Default type: %s, With option type: %s', typeof normal.key, typeof nativeBigInt.key); -#### options.useBigInt, boolean, default false -Specifies if parser uses native BigInt instead of bignumber.js +``` -### Links: -- [RFC4627: The application/json Media Type for JavaScript Object Notation (JSON)](http://www.ietf.org/rfc/rfc4627.txt) -- [Re: \[Json\] Limitations on number size?](http://www.ietf.org/mail-archive/web/json/current/msg00297.html) -- [Is there any proper way to parse JSON with large numbers? (long, bigint, int64)](http://stackoverflow.com/questions/18755125/node-js-is-there-any-proper-way-to-parse-json-with-large-numbers-long-bigint) -- [What is JavaScript's Max Int? What's the highest Integer value a Number can go to without losing precision?](http://stackoverflow.com/questions/307179/what-is-javascripts-max-int-whats-the-highest-integer-value-a-number-can-go-t) -- [Large numbers erroneously rounded in Javascript](http://stackoverflow.com/questions/1379934/large-numbers-erroneously-rounded-in-javascript) +Output +``` +Storing the Number as native BigInt, instead of a BigNumber +Input: { "key": 993143214321423154315154321 } +Default type: object, With option type: bigint -### Native BigInt support +``` -#### Stringifying -Full support out-of-the-box, stringifies BigInts as pure numbers (no quotes, no `n`). +#### options.alwaysParseAsBig, boolean, default false +Specifies if all numbers should be stored as BigNumber. + +Note that this is a dangerous behavior as it breaks the default functionality of being able to convert back-and-forth without data type changes (as this will convert all Number to be-and-stay BigNumber) -#### Parsing +example: ```js -var JSONbig = require('json-bigint')({"useNativeBigInt": true}); +var JSONbig = require('json-bigint'); +var JSONbigAlways = require('json-bigint')({"alwaysParseAsBig": true}); +var key = '{ "key": 123 }'; // there is no need for BigNumber by default, but we're forcing it +console.log(`\n\nStoring the Number as a BigNumber, instead of a Number`); +console.log('Input:', key); +var normal = JSONbig.parse(key); +var always = JSONbigAlways.parse(key); +console.log('Default type: %s, With option type: %s', typeof normal.key, typeof always.key); + ``` -If you want to force all numbers to be parsed as `bignumber.js` -```js -var JSONbig = require('json-bigint')({"alwaysParseAsBig": true}); + +Output +``` +Storing the Number as a BigNumber, instead of a Number +Input: { "key": 123 } +Default type: number, With option type: object + ``` -If you want to force all numbers to be parsed as `BigInt`s + +If you want to force all numbers to be parsed as native `BigInt` (you probably do! Otherwise any calulations become a real headache): ```js var JSONbig = require('json-bigint')({"alwaysParseAsBig": true, "useNativeBigInt": true}); ``` +### Links: +- [RFC4627: The application/json Media Type for JavaScript Object Notation (JSON)](http://www.ietf.org/rfc/rfc4627.txt) +- [Re: \[Json\] Limitations on number size?](http://www.ietf.org/mail-archive/web/json/current/msg00297.html) +- [Is there any proper way to parse JSON with large numbers? (long, bigint, int64)](http://stackoverflow.com/questions/18755125/node-js-is-there-any-proper-way-to-parse-json-with-large-numbers-long-bigint) +- [What is JavaScript's Max Int? What's the highest Integer value a Number can go to without losing precision?](http://stackoverflow.com/questions/307179/what-is-javascripts-max-int-whats-the-highest-integer-value-a-number-can-go-t) +- [Large numbers erroneously rounded in Javascript](http://stackoverflow.com/questions/1379934/large-numbers-erroneously-rounded-in-javascript) + +### Note on native BigInt support + +#### Stringifying +Full support out-of-the-box, stringifies BigInts as pure numbers (no quotes, no `n`) + #### Limitations -Currently `s === JSONbig.stringify(JSONbig.parse(s))` but `o !== JSONbig.parse(JSONbig.stringify(o))` when `o` has value of something like `123n`, `JSONbig` stringify this as `123`, which becomes `number` when being reparsed. -There is currently no consistent way to deal with this issue, so we decided to leave it this way, handling this specific case is then up to users. \ No newline at end of file +- Roundtrip operations + +`s === JSONbig.stringify(JSONbig.parse(s))` but + +`o !== JSONbig.parse(JSONbig.stringify(o))` + +when `o` has a value with something like `123n`. + +`JSONbig` stringify `123n` as `123`, which becomes `number` (aka `123` not `123n`) by default when being reparsed. + +There is currently no consistent way to deal with this issue, so we decided to leave it, handling this specific case is then up to users. \ No newline at end of file