json-with-bigint 2.4.2 → 3.1.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 +11 -5
- package/__tests__/performance.js +141 -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
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ JS library that allows you to easily serialize and deserialize data with BigInt
|
|
|
8
8
|
|
|
9
9
|
1. You need to convert some data to/from JSON and it includes BigInt values
|
|
10
10
|
2. Native JSON.stringify() and JSON.parse() methods in JS can't work with BigInt
|
|
11
|
-
3. Other libraries and pieces of code that you'll find either can't solve this problem while supporting consistent round-trip operations (meaning, you will not get the same BigInt values if you serialize and then deserialize them) or requires you to change your JSON or the way you want to work with your data
|
|
11
|
+
3. Other libraries and pieces of code that you'll find either can't solve this problem while supporting consistent round-trip operations (meaning, you will not get the same BigInt values if you serialize and then deserialize them) or requires you to specify which properties in JSON include BigInt values, or to change your JSON or the way you want to work with your data
|
|
12
12
|
|
|
13
13
|
## Good things about JSON-with-BigInt
|
|
14
14
|
|
|
@@ -20,9 +20,13 @@ JSONStringify(data) // '{"bigNumber":9007199254740992}'
|
|
|
20
20
|
JSONParse(JSONStringify(data)).bigNumber === 9007199254740992n // true
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
✔️ No need to specify which properties in JSON include BigInt values. Library will find them itself
|
|
24
|
+
|
|
23
25
|
✔️ No need to change your JSON or the way you want to work with your data
|
|
24
26
|
|
|
25
|
-
✔️
|
|
27
|
+
✔️ You don't have to memorize this library's API, you already know it. Just skip the dot, use camelCase and that's it (JSONParse(), JSONStringify())
|
|
28
|
+
|
|
29
|
+
✔️ Parses and stringifies all other values other than big numbers the same way as native JSON methods in JS do. You can just replace every `JSON.parse` and `JSON.stringify` call in your project with functions from this library and it'll work.
|
|
26
30
|
|
|
27
31
|
✔️ Correctly parses float numbers and negative numbers
|
|
28
32
|
|
|
@@ -30,14 +34,16 @@ JSONParse(JSONStringify(data)).bigNumber === 9007199254740992n // true
|
|
|
30
34
|
|
|
31
35
|
✔️ Does not contaminate your global space (unlike monkey-patching solution)
|
|
32
36
|
|
|
33
|
-
✔️ You don't have to memorize this library's API, you already know it. Just skip the dot, use camelCase and that's it (JSONParse(), JSONStringify())
|
|
34
|
-
|
|
35
37
|
✔️ Can be used in both JavaScript and TypeScript projects (.d.ts file included)
|
|
36
38
|
|
|
37
|
-
✔️
|
|
39
|
+
✔️ Can be used as both ESM and CommonJS module
|
|
40
|
+
|
|
41
|
+
✔️ Size: 624 bytes (minified and gzipped)
|
|
38
42
|
|
|
39
43
|
✔️ No dependencies
|
|
40
44
|
|
|
45
|
+
✔️ Covered by tests
|
|
46
|
+
|
|
41
47
|
## Getting Started
|
|
42
48
|
|
|
43
49
|
This library has no default export. [Why it's a good thing](https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// ------ Performance tests ------
|
|
2
|
+
const { performance } = require("perf_hooks");
|
|
3
|
+
const { JSONParse, JSONStringify } = require("../json-with-bigint.cjs");
|
|
4
|
+
|
|
5
|
+
const fs = require("fs").promises;
|
|
6
|
+
const https = require("https");
|
|
7
|
+
|
|
8
|
+
// JSON is located in a separate GitHub repo here (click Raw to see the URL below):
|
|
9
|
+
// https://github.com/Ivan-Korolenko/json-with-bigint.performance.json/blob/main/performance.json
|
|
10
|
+
const JSON_URL =
|
|
11
|
+
"https://raw.githubusercontent.com/Ivan-Korolenko/json-with-bigint.performance.json/refs/heads/main/performance.json";
|
|
12
|
+
const JSON_LOCAL_FILEPATH = "__tests__/performance.json";
|
|
13
|
+
|
|
14
|
+
async function fetchJSON(url, maxRetries = 3, delay = 1000) {
|
|
15
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
16
|
+
try {
|
|
17
|
+
const response = await new Promise((resolve, reject) => {
|
|
18
|
+
https
|
|
19
|
+
.get(url, (res) => {
|
|
20
|
+
if (res.statusCode >= 500 && res.statusCode < 600) {
|
|
21
|
+
reject(new Error(`Server error ${res.statusCode}: Retrying...`));
|
|
22
|
+
} else if (res.statusCode !== 200) {
|
|
23
|
+
reject(
|
|
24
|
+
new Error(
|
|
25
|
+
`Request failed with status ${res.statusCode} ${res.statusMessage}`
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let data = "";
|
|
31
|
+
res.on("data", (chunk) => {
|
|
32
|
+
data += chunk;
|
|
33
|
+
});
|
|
34
|
+
res.on("end", () => resolve(data));
|
|
35
|
+
})
|
|
36
|
+
.on("error", reject);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return JSON.parse(response);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (attempt < maxRetries) {
|
|
42
|
+
console.warn(`Attempt ${attempt} failed: ${error.message}`);
|
|
43
|
+
await new Promise((res) =>
|
|
44
|
+
setTimeout(res, delay * Math.pow(2, attempt - 1))
|
|
45
|
+
); // Exponential backoff
|
|
46
|
+
} else {
|
|
47
|
+
console.error("Max retries reached. Fetch failed:", error);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function saveJSONToFile(filePath, data) {
|
|
55
|
+
try {
|
|
56
|
+
const jsonString = JSON.stringify(data, null, 2);
|
|
57
|
+
const tempPath = `${filePath}.tmp`;
|
|
58
|
+
|
|
59
|
+
await fs.writeFile(tempPath, jsonString, "utf8");
|
|
60
|
+
await fs.rename(tempPath, filePath); // Atomic write
|
|
61
|
+
console.log(`✅ JSON data saved to ${filePath}`);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error("Error saving JSON to file:", error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function fetchAndSaveJSON(url, filePath) {
|
|
69
|
+
try {
|
|
70
|
+
const jsonData = await fetchJSON(url);
|
|
71
|
+
|
|
72
|
+
await saveJSONToFile(filePath, jsonData);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("❌ Operation failed:", error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// If the file was downloaded earlier, use it. Otherwise, download
|
|
79
|
+
// After n attempts, give up and throw an error
|
|
80
|
+
async function readPerformanceJSON(
|
|
81
|
+
filePath,
|
|
82
|
+
encoding,
|
|
83
|
+
maxAttempts = 3,
|
|
84
|
+
attempt = 0
|
|
85
|
+
) {
|
|
86
|
+
if (attempt === maxAttempts)
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Reading performance JSON failed after ${attempt} attempts. Check download URL, file availability on that URL, and local filepath`
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const data = await fs.readFile(filePath, encoding);
|
|
93
|
+
|
|
94
|
+
return data;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error.code === "ENOENT") {
|
|
97
|
+
console.log(
|
|
98
|
+
`File not found. Downloading... (Attempt ${attempt + 1}/${maxAttempts})`
|
|
99
|
+
);
|
|
100
|
+
await fetchAndSaveJSON(JSON_URL, filePath);
|
|
101
|
+
|
|
102
|
+
return await readPerformanceJSON(
|
|
103
|
+
filePath,
|
|
104
|
+
encoding,
|
|
105
|
+
maxAttempts,
|
|
106
|
+
attempt + 1
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
console.error("Error reading file:", error);
|
|
110
|
+
throw error; // Re-throw to avoid silent failures
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const measureExecTime = (fn) => {
|
|
116
|
+
const startTime = performance.now();
|
|
117
|
+
|
|
118
|
+
fn();
|
|
119
|
+
|
|
120
|
+
const endTime = performance.now();
|
|
121
|
+
|
|
122
|
+
console.log("Time: ", endTime - startTime);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
async function main() {
|
|
126
|
+
const data = await readPerformanceJSON(JSON_LOCAL_FILEPATH, "utf8");
|
|
127
|
+
|
|
128
|
+
measureExecTime(() => {
|
|
129
|
+
console.log("___________");
|
|
130
|
+
console.log("Performance test. One-way");
|
|
131
|
+
JSONParse(data);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
measureExecTime(() => {
|
|
135
|
+
console.log("___________");
|
|
136
|
+
console.log("Performance test. Round-trip");
|
|
137
|
+
JSONStringify(JSONParse(data));
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
main();
|
|
@@ -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.1.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"
|