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.
- package/README.md +12 -6
- package/__tests__/performance.js +29 -0
- package/__tests__/performance.json +1319112 -0
- package/__tests__/unit.js +137 -0
- package/json-with-bigint.cjs +84 -0
- package/json-with-bigint.js +55 -31
- package/json-with-bigint.min.js +1 -1
- package/package.json +5 -2
|
@@ -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 };
|
package/json-with-bigint.js
CHANGED
|
@@ -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
|
|
10
|
+
const bigInts = /([\[:])?"(-?\d+)n"($|[,\}\]])/g;
|
|
11
|
+
const noise = /([\[:])?("-?\d+n+)n("$|"[,\}\]])/g;
|
|
12
|
+
const convertedToCustomJSON = JSON.stringify(
|
|
10
13
|
data,
|
|
11
|
-
(_, 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
|
|
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
|
|
27
|
+
return denoisedJSON;
|
|
17
28
|
};
|
|
18
29
|
|
|
19
30
|
/*
|
|
20
|
-
Function to parse JSON
|
|
21
|
-
If JSON has values
|
|
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
|
|
46
|
-
|
|
47
|
-
const
|
|
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(
|
|
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
|
};
|
package/json-with-bigint.min.js
CHANGED
|
@@ -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=
|
|
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": "
|
|
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":
|
|
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"
|