From 0bf67783960adc83b15ed7927f210758a2b8382e Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Mon, 20 Oct 2025 14:48:41 +0800 Subject: [PATCH 1/5] Remove json-bigint dependency --- lib/utils.js | 63 ++++++++++++++++++++++++++++++++++ package.json | 1 - rosidl_parser/rosidl_parser.js | 31 ++--------------- 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index e851c84d..93b9c863 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -298,6 +298,67 @@ function compareVersions(version1, version2, operator) { } } +/** + * Parse JSON string with support for large integers (beyond Number.MAX_SAFE_INTEGER). + * Large integers are automatically converted to strings to preserve precision. + * + * For Node.js >= 21.0.0, uses native JSON.parse() with context parameter. + * For older versions, preprocesses the JSON string with regex to convert unsafe integers. + * + * Replaces: json-bigint package + * + * @param {string} str - JSON string to parse + * @returns {any} Parsed JSON object with unsafe integers as strings + * + * @example + * parseJSONWithBigInt('{"value": 9007199254740992}') + * // Returns: { value: '9007199254740992' } + * + * parseJSONWithBigInt('{"value": 100}') + * // Returns: { value: 100 } + */ +function parseJSONWithBigInt(str) { + const contextSupportedVersion = '21.0.0.0'; + const currentVersion = process.version; + const isContextSupported = compareVersions( + currentVersion.substring(1, currentVersion.length), + contextSupportedVersion, + '>=' + ); + + // For nodejs >= 21.0.0, we leverage context parameter to + // convert unsafe integer to string. + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse + if (isContextSupported) { + return JSON.parse(str, (key, value, context) => { + if ( + Number.isInteger(value) && + !Number.isSafeInteger(Number(context.source)) + ) { + return context.source; + } + return value; + }); + } + + // For older Node.js versions, preprocess the JSON string to convert + // large integers (outside the safe integer range) to strings before parsing. + // This replaces the functionality of json-bigint package. + const processed = str.replace( + /:\s*(-?\d+)(?=\s*[,}\]])/g, + (match, number) => { + const num = Number(number); + // If the number is not safe, keep it as a string + if (!Number.isSafeInteger(num)) { + return `: "${number}"`; + } + return match; + } + ); + + return JSON.parse(processed); +} + module.exports = { // General utilities detectUbuntuCodename, @@ -320,5 +381,7 @@ module.exports = { removeSync, readJsonSync, + // Version and JSON utilities compareVersions, + parseJSONWithBigInt, }; diff --git a/package.json b/package.json index 090893bb..c420092f 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "@rclnodejs/ref-struct-di": "^1.1.1", "bindings": "^1.5.0", "debug": "^4.4.0", - "json-bigint": "^1.0.0", "node-addon-api": "^8.3.1", "walk": "^2.3.15" }, diff --git a/rosidl_parser/rosidl_parser.js b/rosidl_parser/rosidl_parser.js index 04e2e52b..e921bde5 100644 --- a/rosidl_parser/rosidl_parser.js +++ b/rosidl_parser/rosidl_parser.js @@ -14,20 +14,12 @@ 'use strict'; -const { compareVersions } = require('../lib/utils.js'); +const { compareVersions, parseJSONWithBigInt } = require('../lib/utils'); const path = require('path'); const execFile = require('child_process').execFile; const pythonExecutable = require('./py_utils').getPythonExecutable('python3'); -const contextSupportedVersion = '21.0.0.0'; -const currentVersion = process.version; -const isContextSupported = compareVersions( - currentVersion.substring(1, currentVersion.length), - contextSupportedVersion, - '>=' -); - const rosidlParser = { parseMessageFile(packageName, filePath) { return this._parseFile('parse_message_file', packageName, filePath); @@ -41,25 +33,6 @@ const rosidlParser = { return this._parseFile('parse_action_file', packageName, filePath); }, - _parseJSONObject(str) { - // For nodejs >= `contextSupportedVersion`, we leverage context parameter to - // convert unsafe integer to string, otherwise, json-bigint is used. - // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse - if (isContextSupported) { - return JSON.parse(str, (key, value, context) => { - if ( - Number.isInteger(value) && - !Number.isSafeInteger(Number(context.source)) - ) { - return context.source; - } - return value; - }); - } - const JSONbigString = require('json-bigint')({ storeAsString: true }); - return JSONbigString.parse(str); - }, - _parseFile(command, packageName, filePath) { return new Promise((resolve, reject) => { const args = [ @@ -82,7 +55,7 @@ const rosidlParser = { ) ); } else { - resolve(this._parseJSONObject(stdout)); + resolve(parseJSONWithBigInt(stdout)); } } ); From 0ffd123c4ce96b7364b9a39434852b84104f77ce Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Tue, 21 Oct 2025 16:16:21 +0800 Subject: [PATCH 2/5] Address comments --- lib/utils.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 93b9c863..7a82ccc3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -298,6 +298,14 @@ function compareVersions(version1, version2, operator) { } } +// Module-level constants for parseJSONWithBigInt +const CONTEXT_SUPPORTED_VERSION = '21.0.0.0'; +const isContextSupported = compareVersions( + process.version.substring(1), + CONTEXT_SUPPORTED_VERSION, + '>=' +); + /** * Parse JSON string with support for large integers (beyond Number.MAX_SAFE_INTEGER). * Large integers are automatically converted to strings to preserve precision. @@ -318,16 +326,7 @@ function compareVersions(version1, version2, operator) { * // Returns: { value: 100 } */ function parseJSONWithBigInt(str) { - const contextSupportedVersion = '21.0.0.0'; - const currentVersion = process.version; - const isContextSupported = compareVersions( - currentVersion.substring(1, currentVersion.length), - contextSupportedVersion, - '>=' - ); - - // For nodejs >= 21.0.0, we leverage context parameter to - // convert unsafe integer to string. + // For Node.js >= 21.0.0, use native context parameter // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse if (isContextSupported) { return JSON.parse(str, (key, value, context) => { @@ -344,13 +343,18 @@ function parseJSONWithBigInt(str) { // For older Node.js versions, preprocess the JSON string to convert // large integers (outside the safe integer range) to strings before parsing. // This replaces the functionality of json-bigint package. + // The regex matches integers that are actual JSON values, not inside strings. + // It matches after: colon (:), comma (,), or open bracket ([) + // and before: comma (,), close brace (}), or close bracket (]) const processed = str.replace( - /:\s*(-?\d+)(?=\s*[,}\]])/g, + /(?:[:,[])\s*(-?\d+)(?=\s*[,}\]])/g, (match, number) => { const num = Number(number); - // If the number is not safe, keep it as a string + // If the number is not safe, convert it to a string if (!Number.isSafeInteger(num)) { - return `: "${number}"`; + // Preserve the prefix character (: or , or [) + const prefix = match.charAt(0); + return `${prefix} "${number}"`; } return match; } From 1342eeab27fb4e499d6dd7ee9db70f2b3fe7d28d Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Wed, 22 Oct 2025 13:49:32 +0800 Subject: [PATCH 3/5] Address comments --- lib/utils.js | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 7a82ccc3..157794bd 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -298,22 +298,31 @@ function compareVersions(version1, version2, operator) { } } -// Module-level constants for parseJSONWithBigInt -const CONTEXT_SUPPORTED_VERSION = '21.0.0.0'; -const isContextSupported = compareVersions( - process.version.substring(1), - CONTEXT_SUPPORTED_VERSION, - '>=' -); +// Feature detection for JSON.parse context parameter support +// Test once at module load time to avoid repeated checks +let isContextSupported = false; +try { + // Try to use context parameter with a simple test + JSON.parse('{"test":1}', (key, value, context) => { + if (context && context.source !== undefined) { + isContextSupported = true; + } + return value; + }); +} catch (e) { + // Context parameter not supported, will use regex fallback + isContextSupported = false; +} /** * Parse JSON string with support for large integers (beyond Number.MAX_SAFE_INTEGER). * Large integers are automatically converted to strings to preserve precision. * - * For Node.js >= 21.0.0, uses native JSON.parse() with context parameter. - * For older versions, preprocesses the JSON string with regex to convert unsafe integers. - * - * Replaces: json-bigint package + * Uses feature detection to determine the best parsing strategy: + * - If context parameter is supported (Node.js >= 21.0.0, modern runtimes): + * Uses native JSON.parse() with context parameter for optimal performance + * - Otherwise (older environments): + * Preprocesses JSON string with regex to convert unsafe integers before parsing * * @param {string} str - JSON string to parse * @returns {any} Parsed JSON object with unsafe integers as strings @@ -326,7 +335,7 @@ const isContextSupported = compareVersions( * // Returns: { value: 100 } */ function parseJSONWithBigInt(str) { - // For Node.js >= 21.0.0, use native context parameter + // For environments supporting context parameter, use it // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse if (isContextSupported) { return JSON.parse(str, (key, value, context) => { @@ -340,9 +349,9 @@ function parseJSONWithBigInt(str) { }); } - // For older Node.js versions, preprocess the JSON string to convert - // large integers (outside the safe integer range) to strings before parsing. - // This replaces the functionality of json-bigint package. + // For environments without context parameter support, preprocess the JSON string + // to convert large integers (outside the safe integer range) to strings before parsing. + // // The regex matches integers that are actual JSON values, not inside strings. // It matches after: colon (:), comma (,), or open bracket ([) // and before: comma (,), close brace (}), or close bracket (]) From 32749f4a22d615ba7a6f1b013c940b0a204756cd Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Wed, 22 Oct 2025 14:13:51 +0800 Subject: [PATCH 4/5] Address comments --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 157794bd..5dd83a3c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -309,7 +309,7 @@ try { } return value; }); -} catch (e) { +} catch { // Context parameter not supported, will use regex fallback isContextSupported = false; } From 6412a0b6980ba0bf664a3292be875eb3f128d94d Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Wed, 22 Oct 2025 16:57:35 +0800 Subject: [PATCH 5/5] Address comments --- lib/utils.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 5dd83a3c..7bd17ae0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -358,12 +358,22 @@ function parseJSONWithBigInt(str) { const processed = str.replace( /(?:[:,[])\s*(-?\d+)(?=\s*[,}\]])/g, (match, number) => { - const num = Number(number); - // If the number is not safe, convert it to a string - if (!Number.isSafeInteger(num)) { - // Preserve the prefix character (: or , or [) - const prefix = match.charAt(0); - return `${prefix} "${number}"`; + // Use BigInt to check if the number is safe without losing precision + // BigInt can accurately represent arbitrarily large integers + try { + const bigIntValue = BigInt(number); + // Check if the value is outside the safe integer range + if ( + bigIntValue < BigInt(Number.MIN_SAFE_INTEGER) || + bigIntValue > BigInt(Number.MAX_SAFE_INTEGER) + ) { + // Preserve the prefix character (: or , or [) + const prefix = match.charAt(0); + return `${prefix} "${number}"`; + } + } catch { + // If BigInt conversion fails, keep the original match + return match; } return match; }