From 77731ab83cdd552382c5e121edf864195f01b8a7 Mon Sep 17 00:00:00 2001 From: zhangjing Date: Thu, 31 Jul 2025 19:12:48 +0800 Subject: [PATCH] feat: handle large number --- lib/safe-parse.js | 40 +++++++ lib/verify-stream.js | 21 ++-- test/safeJsonParseWithBigInt.test.js | 151 +++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 12 deletions(-) create mode 100644 lib/safe-parse.js create mode 100644 test/safeJsonParseWithBigInt.test.js diff --git a/lib/safe-parse.js b/lib/safe-parse.js new file mode 100644 index 0000000..e641e2a --- /dev/null +++ b/lib/safe-parse.js @@ -0,0 +1,40 @@ +function isObject(thing) { + return Object.prototype.toString.call(thing) === "[object Object]"; +} + +function safeJsonParse(thing) { + if (isObject(thing)) return thing; + try { + return JSON.parse(thing); + } catch (e) { + return undefined; + } +} + +// Custom JSON parser that preserves big integer precision +function safeJsonParseWithBigInt(text) { + // Use regex to match big integers (including negative numbers) and convert them to strings + var processedText = text.replace( + /:(\s*)(-?\d{16,})/g, + function (match, space, number) { + // If the number exceeds the safe integer range, convert it to a string + if ( + parseInt(number) > Number.MAX_SAFE_INTEGER || + parseInt(number) < Number.MIN_SAFE_INTEGER + ) { + return ":" + space + '"' + number + '"'; + } + return match; + } + ); + + try { + return JSON.parse(processedText); + } catch (e) { + // If the processed text fails to parse, try the original text + return JSON.parse(text); + } +} + +exports.safeJsonParse = safeJsonParse; +exports.safeJsonParseWithBigInt = safeJsonParseWithBigInt; diff --git a/lib/verify-stream.js b/lib/verify-stream.js index 39f7c73..efb5e73 100644 --- a/lib/verify-stream.js +++ b/lib/verify-stream.js @@ -7,16 +7,7 @@ var toString = require('./tostring'); var util = require('util'); var JWS_REGEX = /^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/; -function isObject(thing) { - return Object.prototype.toString.call(thing) === '[object Object]'; -} - -function safeJsonParse(thing) { - if (isObject(thing)) - return thing; - try { return JSON.parse(thing); } - catch (e) { return undefined; } -} +var { safeJsonParse, safeJsonParseWithBigInt } = require("./safe-parse"); function headerFromJWS(jwsSig) { var encodedHeader = jwsSig.split('.', 1)[0]; @@ -67,8 +58,14 @@ function jwsDecode(jwsSig, opts) { return null; var payload = payloadFromJWS(jwsSig); - if (header.typ === 'JWT' || opts.json) - payload = JSON.parse(payload, opts.encoding); + if (header.typ === 'JWT' || opts.json) { + // Use custom parser to preserve big integer precision + if (opts.preserveBigInt !== false) { + payload = safeJsonParseWithBigInt(payload); + } else { + payload = JSON.parse(payload, opts.encoding); + } + } return { header: header, diff --git a/test/safeJsonParseWithBigInt.test.js b/test/safeJsonParseWithBigInt.test.js new file mode 100644 index 0000000..10bf77a --- /dev/null +++ b/test/safeJsonParseWithBigInt.test.js @@ -0,0 +1,151 @@ +/*global process*/ +const test = require("tape"); +const { safeJsonParseWithBigInt } = require("../lib/safe-parse"); + +test("safeJsonParseWithBigInt: Basic functionality test", function (t) { + // Use the specified payload string + const payloadString = '{"userId":13123213123163776887779878}'; + + const result = safeJsonParseWithBigInt(payloadString); + + t.ok(result !== null, "Parse should succeed"); + t.equal( + typeof result.userId, + "string", + "Big integer should be parsed as string" + ); + t.equal( + result.userId, + "13123213123163776887779878", + "Big integer should maintain precision" + ); + + // Verify precision is maintained + const originalNumber = 13123213123163776887779878; + t.notEqual( + result.userId, + originalNumber, + "Parsed value should not equal original number (due to precision loss)" + ); + t.equal( + result.userId, + "13123213123163776887779878", + "Parsed value should equal the string representation of original number" + ); + + t.end(); +}); + +test("safeJsonParseWithBigInt: Boundary value test", function (t) { + const testCases = [ + { + name: "Maximum safe integer", + json: '{"num":9007199254740991}', + expectedType: "number", + expectedValue: 9007199254740991, + }, + { + name: "Exceeds safe integer range", + json: '{"num":9007199254740992}', + expectedType: "string", + expectedValue: "9007199254740992", + }, + { + name: "Very large number", + json: '{"num":123456789012345678901234567890}', + expectedType: "string", + expectedValue: "123456789012345678901234567890", + }, + { + name: "Minimum safe integer", + json: '{"num":-9007199254740991}', + expectedType: "number", + expectedValue: -9007199254740991, + }, + { + name: "Exceeds safe integer range (negative)", + json: '{"num":-9007199254740992}', + expectedType: "string", + expectedValue: "-9007199254740992", + }, + ]; + + testCases.forEach(function (testCase) { + const result = safeJsonParseWithBigInt(testCase.json); + + t.equal( + typeof result.num, + testCase.expectedType, + `${testCase.name}: Number type should be correct` + ); + t.same( + result.num, + testCase.expectedValue, + `${testCase.name}: Number value should match` + ); + }); + + t.end(); +}); + +test("safeJsonParseWithBigInt: Complex object test", function (t) { + const complexJson = + '{"user":{"id":13123213123163776887779878,"name":"test"},"normalNumber":12345,"bigNumber":987654321098765432109876543210}'; + + const result = safeJsonParseWithBigInt(complexJson); + + t.ok(result !== null, "Complex object parsing should succeed"); + t.equal( + typeof result.user.id, + "string", + "Big integer in nested object should be parsed as string" + ); + t.equal( + result.user.id, + "13123213123163776887779878", + "Big integer in nested object should maintain precision" + ); + t.equal( + typeof result.normalNumber, + "number", + "Normal number should remain as number type" + ); + t.equal( + typeof result.bigNumber, + "string", + "Big integer should be parsed as string" + ); + t.equal( + result.bigNumber, + "987654321098765432109876543210", + "Big integer should maintain precision" + ); + + t.end(); +}); + +test("safeJsonParseWithBigInt: Array test", function (t) { + const arrayJson = + '[{"id":13123213123163776887779878,"name":"user1"},{"id":12345,"name":"user2"}]'; + + const result = safeJsonParseWithBigInt(arrayJson); + + t.ok(Array.isArray(result), "Result should be an array"); + t.equal( + typeof result[0].id, + "string", + "Big integer in array should be parsed as string" + ); + t.equal( + result[0].id, + "13123213123163776887779878", + "Big integer in array should maintain precision" + ); + t.equal( + typeof result[1].id, + "number", + "Normal number in array should remain as number type" + ); + + t.end(); +});