json-with-bigint 2.4.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ // ------ Unit tests ------
2
+ const assert = require("assert");
3
+ const { JSONStringify, JSONParse } = require("../json-with-bigint.cjs");
4
+
5
+ const test1JSON = `{"zero":9007199254740998,"one":-42,"two":-9007199254740998,"test":["He was\\":[-23432432432434324324324324]",111,9007199254740998,{"test2":-9007199254740998}],"test3":["He was:[-23432432432434324324324324]",111,9007199254740998,{"test2":-9007199254740998,"float":1.9007199254740998,"float2":0.1,"float3":2.9007199254740996,"int":1,"int2":3243243432432434324324324}],"float4":[1.9007199254740998,1111111111111111111111111111111111,0.1,1,54354654654654654654656546546546546]}`;
6
+ const test1Obj = {
7
+ zero: 9007199254740998n,
8
+ one: -42,
9
+ two: -9007199254740998n,
10
+ test: [
11
+ 'He was":[-23432432432434324324324324]',
12
+ 111,
13
+ 9007199254740998n,
14
+ { test2: -9007199254740998n },
15
+ ],
16
+ test3: [
17
+ "He was:[-23432432432434324324324324]",
18
+ 111,
19
+ 9007199254740998n,
20
+ {
21
+ test2: -9007199254740998n,
22
+ float: 1.9007199254740998,
23
+ float2: 0.1,
24
+ float3: 2.9007199254740996,
25
+ int: 1,
26
+ int2: 3243243432432434324324324n,
27
+ },
28
+ ],
29
+ float4: [
30
+ 1.9007199254740998,
31
+ 1111111111111111111111111111111111n,
32
+ 0.1,
33
+ 1,
34
+ 54354654654654654654656546546546546n,
35
+ ],
36
+ };
37
+
38
+ const test2JSON = `{
39
+ "test": [
40
+ {
41
+ "ID": 1035342667379599058,
42
+ "Timestamp": "2022-09-13 22:21:25",
43
+ "Contents": "broken example message",
44
+ "Contents2": "broken example 1035342667379599058 message",
45
+ "Contents3": "broken example [1035342667379599058, 1035342667379599058] message",
46
+ "Attachments": "",
47
+ "BreakingValue": 54354654654654654654656546546546546
48
+ },
49
+ 1035342667379599058,
50
+ -1.1035342667379599058,
51
+ 1035342667379599058
52
+ ],
53
+ "test2": {
54
+ "1035342667379599058": 1035342667379599058
55
+ }
56
+ }`;
57
+ // Special case, because native JSON.parse strips \n and whitespaces.
58
+ // So technically, a true round-trip operation (including all spaces, etc.) is not possible in the case of pretty JSON without writing your own full JSON.parse implementation for this particular case.
59
+ // In practice, though, it shouldn't cause any problems, because data will work fine even in that case, and if the backend expects a prettified JSON, you can just prettify it before sending it.
60
+ const test2TersedJSON = `{"test":[{"ID":1035342667379599058,"Timestamp":"2022-09-13 22:21:25","Contents":"broken example message","Contents2":"broken example 1035342667379599058 message","Contents3":"broken example [1035342667379599058, 1035342667379599058] message","Attachments":"","BreakingValue":54354654654654654654656546546546546},1035342667379599058,-1.10353426673796,1035342667379599058],"test2":{"1035342667379599058":1035342667379599058}}`;
61
+ const test2Obj = {
62
+ test: [
63
+ {
64
+ ID: 1035342667379599058n,
65
+ Timestamp: "2022-09-13 22:21:25",
66
+ Contents: "broken example message",
67
+ Contents2: "broken example 1035342667379599058 message",
68
+ Contents3:
69
+ "broken example [1035342667379599058, 1035342667379599058] message",
70
+ Attachments: "",
71
+ BreakingValue: 54354654654654654654656546546546546n,
72
+ },
73
+ 1035342667379599058n,
74
+ -1.10353426673796,
75
+ 1035342667379599058n,
76
+ ],
77
+ test2: { "1035342667379599058": 1035342667379599058n },
78
+ };
79
+
80
+ const test3JSON = `{"items":[{"message":"some text 17365091955960356025, some text"}]}`;
81
+ const test3Obj = {
82
+ items: [{ message: "some text 17365091955960356025, some text" }],
83
+ };
84
+
85
+ const test4JSON = `9007199254740998`;
86
+ const test4Obj = 9007199254740998n;
87
+
88
+ const test5JSON = `[9007199254740998,[9007199254740998],9007199254740998]`;
89
+ const test5Obj = [9007199254740998n, [9007199254740998n], 9007199254740998n];
90
+
91
+ const test6JSON = `[9007199254740998]`;
92
+ const test6Obj = [9007199254740998n];
93
+
94
+ const test7JSON = `["0a","1b","9n","9nn",90071992547409981111,"90071992547409981111.5n"]`;
95
+ const test7Obj = [
96
+ "0a",
97
+ "1b",
98
+ "9n",
99
+ "9nn",
100
+ 90071992547409981111n,
101
+ "90071992547409981111.5n",
102
+ ];
103
+
104
+ assert.deepStrictEqual(JSONParse(test1JSON), test1Obj);
105
+ console.log("1 test parsing passed");
106
+ assert.deepStrictEqual(JSONStringify(JSONParse(test1JSON)), test1JSON);
107
+ console.log("1 test round-trip passed");
108
+
109
+ assert.deepStrictEqual(JSONParse(test2JSON), test2Obj);
110
+ console.log("2 test parsing passed");
111
+ assert.deepStrictEqual(JSONStringify(JSONParse(test2JSON)), test2TersedJSON);
112
+ console.log("2 test round-trip passed");
113
+
114
+ assert.deepStrictEqual(JSONParse(test3JSON), test3Obj);
115
+ console.log("3 test parsing passed");
116
+ assert.deepStrictEqual(JSONStringify(JSONParse(test3JSON)), test3JSON);
117
+ console.log("3 test round-trip passed");
118
+
119
+ assert.deepStrictEqual(JSONParse(test4JSON), test4Obj);
120
+ console.log("4 test parsing passed");
121
+ assert.deepStrictEqual(JSONStringify(JSONParse(test4JSON)), test4JSON);
122
+ console.log("4 test round-trip passed");
123
+
124
+ assert.deepStrictEqual(JSONParse(test5JSON), test5Obj);
125
+ console.log("5 test parsing passed");
126
+ assert.deepStrictEqual(JSONStringify(JSONParse(test5JSON)), test5JSON);
127
+ console.log("5 test round-trip passed");
128
+
129
+ assert.deepStrictEqual(JSONParse(test6JSON), test6Obj);
130
+ console.log("6 test parsing passed");
131
+ assert.deepStrictEqual(JSONStringify(JSONParse(test6JSON)), test6JSON);
132
+ console.log("6 test round-trip passed");
133
+
134
+ assert.deepStrictEqual(JSONParse(test7JSON), test7Obj);
135
+ console.log("7 test parsing passed");
136
+ assert.deepStrictEqual(JSONStringify(JSONParse(test7JSON)), test7JSON);
137
+ console.log("7 test round-trip passed");
@@ -0,0 +1,84 @@
1
+ const noiseValue = /^-?\d+n+$/; // Noise - strings that match the custom format before being converted to it
2
+
3
+ /*
4
+ Function to serialize data to a JSON string.
5
+ Converts BigInt values to a custom format (strings with digits and "n" at the end) and then converts them to proper big integers in a JSON string.
6
+ */
7
+ const JSONStringify = (data, space) => {
8
+ if (!data) return JSON.stringify(data);
9
+
10
+ const bigInts = /([\[:])?"(-?\d+)n"($|[,\}\]])/g;
11
+ const noise = /([\[:])?("-?\d+n+)n("$|"[,\}\]])/g;
12
+ const convertedToCustomJSON = JSON.stringify(
13
+ data,
14
+ (_, value) => {
15
+ const isNoise =
16
+ typeof value === "string" && Boolean(value.match(noiseValue));
17
+
18
+ if (isNoise) return value.toString() + "n"; // Mark noise values with additional "n" to offset the deletion of one "n" during the processing
19
+
20
+ return typeof value === "bigint" ? value.toString() + "n" : value;
21
+ },
22
+ space
23
+ );
24
+ const processedJSON = convertedToCustomJSON.replace(bigInts, "$1$2$3"); // Delete one "n" off the end of every BigInt value
25
+ const denoisedJSON = processedJSON.replace(noise, "$1$2$3"); // Remove one "n" off the end of every noisy string
26
+
27
+ return denoisedJSON;
28
+ };
29
+
30
+ /*
31
+ Function to parse JSON.
32
+ If JSON has number values greater than Number.MAX_SAFE_INTEGER, we convert those values to a custom format, then parse them to BigInt values.
33
+ Other types of values are not affected and parsed as native JSON.parse() would parse them.
34
+ */
35
+ const JSONParse = (json) => {
36
+ if (!json) return JSON.parse(json);
37
+
38
+ const MAX_INT = Number.MAX_SAFE_INTEGER.toString();
39
+ const MAX_DIGITS = MAX_INT.length;
40
+ const stringsOrLargeNumbers =
41
+ /"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g;
42
+ const noiseValueWithQuotes = /^"-?\d+n+"$/; // Noise - strings that match the custom format before being converted to it
43
+ const customFormat = /^-?\d+n$/;
44
+
45
+ // Find and mark big numbers with "n"
46
+ const serializedData = json.replace(
47
+ stringsOrLargeNumbers,
48
+ (text, digits, fractional, exponential) => {
49
+ const isString = text[0] === '"';
50
+ const isNoise = isString && Boolean(text.match(noiseValueWithQuotes));
51
+
52
+ if (isNoise) return text.substring(0, text.length - 1) + 'n"'; // Mark noise values with additional "n" to offset the deletion of one "n" during the processing
53
+
54
+ const isFractionalOrExponential = fractional || exponential;
55
+ const isLessThanMaxSafeInt =
56
+ digits &&
57
+ (digits.length < MAX_DIGITS ||
58
+ (digits.length === MAX_DIGITS && digits <= MAX_INT)); // With a fixed number of digits, we can correctly use lexicographical comparison to do a numeric comparison
59
+
60
+ if (isString || isFractionalOrExponential || isLessThanMaxSafeInt)
61
+ return text;
62
+
63
+ return '"' + text + 'n"';
64
+ }
65
+ );
66
+
67
+ // Convert marked big numbers to BigInt
68
+ return JSON.parse(serializedData, (_, value) => {
69
+ const isCustomFormatBigInt =
70
+ typeof value === "string" && Boolean(value.match(customFormat));
71
+
72
+ if (isCustomFormatBigInt)
73
+ return BigInt(value.substring(0, value.length - 1));
74
+
75
+ const isNoiseValue =
76
+ typeof value === "string" && Boolean(value.match(noiseValue));
77
+
78
+ if (isNoiseValue) return value.substring(0, value.length - 1); // Remove one "n" off the end of the noisy string
79
+
80
+ return value;
81
+ });
82
+ };
83
+
84
+ module.exports = { JSONStringify, JSONParse };
@@ -1,58 +1,82 @@
1
+ const noiseValue = /^-?\d+n+$/; // Noise - strings that match the custom format before being converted to it
2
+
1
3
  /*
2
- Function to serialize data to JSON string
3
- Converts BigInt values to custom format (strings with digits and "n" at the end) and then converts them to proper big integers in JSON string
4
+ Function to serialize data to a JSON string.
5
+ Converts BigInt values to a custom format (strings with digits and "n" at the end) and then converts them to proper big integers in a JSON string.
4
6
  */
5
7
  export const JSONStringify = (data, space) => {
6
8
  if (!data) return JSON.stringify(data);
7
9
 
8
- const bigInts = /([\[:])?"(-?\d+)n"([,\}\]])/g;
9
- const preliminaryJSON = JSON.stringify(
10
+ const bigInts = /([\[:])?"(-?\d+)n"($|[,\}\]])/g;
11
+ const noise = /([\[:])?("-?\d+n+)n("$|"[,\}\]])/g;
12
+ const convertedToCustomJSON = JSON.stringify(
10
13
  data,
11
- (_, value) => (typeof value === "bigint" ? value.toString() + "n" : value),
14
+ (_, value) => {
15
+ const isNoise =
16
+ typeof value === "string" && Boolean(value.match(noiseValue));
17
+
18
+ if (isNoise) return value.toString() + "n"; // Mark noise values with additional "n" to offset the deletion of one "n" during the processing
19
+
20
+ return typeof value === "bigint" ? value.toString() + "n" : value;
21
+ },
12
22
  space
13
23
  );
14
- const finalJSON = preliminaryJSON.replace(bigInts, "$1$2$3");
24
+ const processedJSON = convertedToCustomJSON.replace(bigInts, "$1$2$3"); // Delete one "n" off the end of every BigInt value
25
+ const denoisedJSON = processedJSON.replace(noise, "$1$2$3"); // Remove one "n" off the end of every noisy string
15
26
 
16
- return finalJSON;
27
+ return denoisedJSON;
17
28
  };
18
29
 
19
30
  /*
20
- Function to parse JSON
21
- If JSON has values presented in a lib's custom format (strings with digits and "n" character at the end), we just parse them to BigInt values (for backward compatibility with previous versions of the lib)
22
- If JSON has values greater than Number.MAX_SAFE_INTEGER, we convert those values to our custom format, then parse them to BigInt values.
31
+ Function to parse JSON.
32
+ If JSON has number values greater than Number.MAX_SAFE_INTEGER, we convert those values to a custom format, then parse them to BigInt values.
23
33
  Other types of values are not affected and parsed as native JSON.parse() would parse them.
24
-
25
- Big numbers are found and marked using a Regular Expression with these conditions:
26
-
27
- 1. Before the match there is no . and one of the following is present:
28
- 1.1 ":
29
- 1.2 ":[
30
- 1.3 ":[anyNumberOf(anyCharacters)
31
- 1.4 , with no ":"anyNumberOf(anyCharacters) before it
32
- All " required in the rule are not escaped. All whitespace and newline characters outside of string values are ignored.
33
-
34
- 2. The match itself has more than 16 digits OR (16 digits and any digit of the number is greater than that of the Number.MAX_SAFE_INTEGER). And it may have a - sign at the start.
35
-
36
- 3. After the match one of the following is present:
37
- 3.1 , without anyNumberOf(anyCharacters) "} or anyNumberOf(anyCharacters) "] after it
38
- 3.2 } without " after it
39
- 3.3 ] without " after it
40
- All whitespace and newline characters outside of string values are ignored.
41
34
  */
42
35
  export const JSONParse = (json) => {
43
36
  if (!json) return JSON.parse(json);
44
37
 
45
- const numbersBiggerThanMaxInt =
46
- /(?<=[^\\]":\n*\s*[\[]?|[^\\]":\n*\s*\[.*[^\.\d*]\n*\s*|(?<![^\\]"\n*\s*:\n*\s*[^\\]".*),\n*\s*)(-?\d{17,}|-?(?:[9](?:[1-9]07199254740991|0[1-9]7199254740991|00[8-9]199254740991|007[2-9]99254740991|007199[3-9]54740991|0071992[6-9]4740991|00719925[5-9]740991|007199254[8-9]40991|0071992547[5-9]0991|00719925474[1-9]991|00719925474099[2-9])))(?=,(?!.*[^\\]"(\n*\s*\}|\n*\s*\]))|\n*\s*\}[^"]?|\n*\s*\][^"])/g;
47
- const serializedData = json.replace(numbersBiggerThanMaxInt, `"$1n"`);
38
+ const MAX_INT = Number.MAX_SAFE_INTEGER.toString();
39
+ const MAX_DIGITS = MAX_INT.length;
40
+ const stringsOrLargeNumbers =
41
+ /"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g;
42
+ const noiseValueWithQuotes = /^"-?\d+n+"$/; // Noise - strings that match the custom format before being converted to it
43
+ const customFormat = /^-?\d+n$/;
44
+
45
+ // Find and mark big numbers with "n"
46
+ const serializedData = json.replace(
47
+ stringsOrLargeNumbers,
48
+ (text, digits, fractional, exponential) => {
49
+ const isString = text[0] === '"';
50
+ const isNoise = isString && Boolean(text.match(noiseValueWithQuotes));
51
+
52
+ if (isNoise) return text.substring(0, text.length - 1) + 'n"'; // Mark noise values with additional "n" to offset the deletion of one "n" during the processing
48
53
 
54
+ const isFractionalOrExponential = fractional || exponential;
55
+ const isLessThanMaxSafeInt =
56
+ digits &&
57
+ (digits.length < MAX_DIGITS ||
58
+ (digits.length === MAX_DIGITS && digits <= MAX_INT)); // With a fixed number of digits, we can correctly use lexicographical comparison to do a numeric comparison
59
+
60
+ if (isString || isFractionalOrExponential || isLessThanMaxSafeInt)
61
+ return text;
62
+
63
+ return '"' + text + 'n"';
64
+ }
65
+ );
66
+
67
+ // Convert marked big numbers to BigInt
49
68
  return JSON.parse(serializedData, (_, value) => {
50
69
  const isCustomFormatBigInt =
51
- typeof value === "string" && Boolean(value.match(/^-?\d+n$/));
70
+ typeof value === "string" && Boolean(value.match(customFormat));
52
71
 
53
72
  if (isCustomFormatBigInt)
54
73
  return BigInt(value.substring(0, value.length - 1));
55
74
 
75
+ const isNoiseValue =
76
+ typeof value === "string" && Boolean(value.match(noiseValue));
77
+
78
+ if (isNoiseValue) return value.substring(0, value.length - 1); // Remove one "n" off the end of the noisy string
79
+
56
80
  return value;
57
81
  });
58
82
  };
@@ -1 +1 @@
1
- export const JSONStringify=(n,t)=>{if(!n)return JSON.stringify(n);return JSON.stringify(n,((n,t)=>"bigint"==typeof t?t.toString()+"n":t),t).replace(/([\[:])?"(-?\d+)n"([,\}\]])/g,"$1$2$3")};export const JSONParse=n=>{if(!n)return JSON.parse(n);const t=n.replace(/(?<=[^\\]":\n*\s*[\[]?|[^\\]":\n*\s*\[.*[^\.\d*]\n*\s*|(?<![^\\]"\n*\s*:\n*\s*[^\\]".*),\n*\s*)(-?\d{17,}|-?(?:[9](?:[1-9]07199254740991|0[1-9]7199254740991|00[8-9]199254740991|007[2-9]99254740991|007199[3-9]54740991|0071992[6-9]4740991|00719925[5-9]740991|007199254[8-9]40991|0071992547[5-9]0991|00719925474[1-9]991|00719925474099[2-9])))(?=,(?!.*[^\\]"(\n*\s*\}|\n*\s*\]))|\n*\s*\}[^"]?|\n*\s*\][^"])/g,'"$1n"');return JSON.parse(t,((n,t)=>"string"==typeof t&&Boolean(t.match(/^-?\d+n$/))?BigInt(t.substring(0,t.length-1)):t))};
1
+ const noiseValue=/^-?\d+n+$/;export const JSONStringify=(n,t)=>{if(!n)return JSON.stringify(n);return JSON.stringify(n,((n,t)=>"string"==typeof t&&Boolean(t.match(noiseValue))||"bigint"==typeof t?t.toString()+"n":t),t).replace(/([\[:])?"(-?\d+)n"($|[,\}\]])/g,"$1$2$3").replace(/([\[:])?("-?\d+n+)n("$|"[,\}\]])/g,"$1$2$3")};export const JSONParse=n=>{if(!n)return JSON.parse(n);const t=Number.MAX_SAFE_INTEGER.toString(),e=t.length,r=/^"-?\d+n+"$/,o=/^-?\d+n$/,i=n.replace(/"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g,((n,o,i,s)=>{const g='"'===n[0];if(g&&Boolean(n.match(r)))return n.substring(0,n.length-1)+'n"';const a=i||s,l=o&&(o.length<e||o.length===e&&o<=t);return g||a||l?n:'"'+n+'n"'}));return JSON.parse(i,((n,t)=>{if("string"==typeof t&&Boolean(t.match(o)))return BigInt(t.substring(0,t.length-1));return"string"==typeof t&&Boolean(t.match(noiseValue))?t.substring(0,t.length-1):t}))};
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "json-with-bigint",
3
- "version": "2.4.1",
3
+ "version": "3.0.0",
4
4
  "description": "JS library that allows you to easily serialize and deserialize data with BigInt values",
5
5
  "type": "module",
6
- "exports": "./json-with-bigint.js",
6
+ "exports": {
7
+ "import": "./json-with-bigint.js",
8
+ "require": "./json-with-bigint.cjs"
9
+ },
7
10
  "repository": {
8
11
  "type": "git",
9
12
  "url": "https://github.com/Ivan-Korolenko/json-with-bigint"