Skip to content

patch(bigint): handle bigint in responses - #5445

Closed
bhavya3024 wants to merge 1 commit into
expressjs:masterfrom
bhavya3024:master
Closed

patch(bigint): handle bigint in responses#5445
bhavya3024 wants to merge 1 commit into
expressjs:masterfrom
bhavya3024:master

Conversation

@bhavya3024

@bhavya3024bhavya3024 commented Feb 5, 2024

Copy link
Copy Markdown
Contributor

converts bigints to numbers recursively in case of objects/arrays.

Hi there!
I have came across a situation in which we were not able to send responses directly due to bigints, earlier I used to handle this logic in my application code but I thought how about let express handles it automatically, so that new application user doesn't have to handle this on their own.

I have written logic in ES5 intentionally such that it doesn't break very old Nodejs versions if this PR gets merged.

I could have thought to write unit tests for the same, but can you confirm which is the minumum nodejs version should I set (is this progamatically possible btw ?) so that these tests should work only for those nodejs versions which supports BigInt.

@mikeal@peters@nick@mcolyer

converts bigints to numbers recursively in case of objects/arrays
Signed-off-by: Bhavya Dhiman <bhavyadhiman7@gmail.com>
@dougwilson

Copy link
Copy Markdown
Contributor

Doesn't this... break big numbers silently? Can you add a test for an actual big number? Or any tests at all? This seems highly dangerous unless perhaps it will error out for big number that cannot be represented as a Number at bare minimum.

Also curious on how this may break existing uses of the replacer for folks who many be doing things like bigints already (lile coverting to a string), so also seems to be a major breaking change too.

Comment threadlib/response.js
case 'boolean':
case 'number':
case 'object':
case 'bigint':

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems strange to add a case here for the type and then still check the type again in the case body. Should this not simply be it's own case and body with a break?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding bigint seperatlely would have made more sense! got it!

Comment threadlib/response.js
// If it's an array, loop over its elements and convert them recursively
if (Array.isArray(obj)) {
for (var index = 0; index < obj.length; index += 1) {
obj[index] = convertBigIntsToNumbers(obj[index]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this mutate the array the user passed into Express, or is this operating on a copy?

Comment threadlib/response.js
var value = obj[key];
// If the value is a BigInt, convert it to a number
if (typeof value === 'bigint') {
obj[key] = Number(value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this mutate the object that is passed into Express, or is this operating on a copy? May need to be very mindfull bc objects may have setters defined or even the property can have only a getter and no setter at all.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per my logic, it is doing mutation,
which one is better, doing mutation or generate a new copy!
I am little confused, if generating a new copy is the better solution, is there any edge case we may lose some properties of the old object in the new object!
even if I do this, i won't recommend using lodash here because it will make package a little more heavy, and clonedeep is slow.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to mutute whatever the user is passing in to res.send as that would be very unexpected and can even cause whatever is done with it to change or be unpredictable. For example the app also passed the object to be saved in the db, but then this is changing the values all of a sudden, changing what gets saved in the db -- or a race condition if this if they are both happening async to each other. The object passed to res.send may even be frozen.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understood the case, in which when we sent the response first and then saved into DB, it will lose the fully original context,
I should have a copy of this.

@dougwilson

Copy link
Copy Markdown
Contributor

I left some comments and just not sure about this in general, as it seems to me how to seralize big numbers is not really universal, and this PR seems to default to silent data loss.

@bhavya3024

Copy link
Copy Markdown
ContributorAuthor

Doesn't this... break big numbers silently? Can you add a test for an actual big number? Or any tests at all? This seems highly dangerous unless perhaps it will error out for big number that cannot be represented as a Number at bare minimum.

Let me check this. I cannot add unit test here now because of older nodejs versions but i will definitely add a screenshot here for sure!

@dougwilson

Copy link
Copy Markdown
Contributor

Just add the tests here, as it definitely won't be landing without tests.

@bhavya3024

Copy link
Copy Markdown
ContributorAuthor

@dougwilson for large numbers,, it represents as an number with expontential like this in the screenshot, whether directly or converting bigint to a number:

image

@bhavya3024

Copy link
Copy Markdown
ContributorAuthor

Just add the tests here, as it definitely won't be landing without tests.

How should I make sure for bigint it won't get blasted in older nodejs versions ? will try to check nodejs version programatically anywhow.

@dougwilson

Copy link
Copy Markdown
Contributor

@bhavya3024 I think you may perhaps misunderstand how bigints work and how numbers work. You can see that there is a data loss when you take a realistic bigint and covert it to a number:

image

We really cannot be landing something in Express that would be causing silent data loss.

Why would you not be able to simply set "json replacer" on your app with your included implementation in order for your app to do this, if the data loss is acceptable to your app? Apps can make such a decision, but Express itself as the framework should not be forcing such a decision on the apps, as it is very surprising behavior.

Also, your function does not handle circular objects and unlike the original res.send which would error, your implementation would allow for an infinite loop, resulting in a denial of service of an application which attempted to send a circular object, also a major issue here.

@bhavya3024

Copy link
Copy Markdown
ContributorAuthor

image
Never tried this before, if I hadn't put a PR I never knew this. This is a data loss for sure, converting a bigint into number and then number to bigint are making old and new bigint unequal. I never knew this. There are some complicated things in JS which most of the developers don't know incliding
@dougwilson I think I need to close this PR because data loss can't be solved. Thanks for the feedback!.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bhavya3024@dougwilson