Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes large numbers parsed incorrectly when using JSON #2236

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

austenadler
Copy link

Description

The json-bigint was being used to parse the request body when JSON was used, but the library would convert large numbers to strings, and even larger numbers to strings in scientific notation.

This PR removes the json-bigint library, and always sends the user's json body without parsing through JSON or JSONbig (but it still passes through decomment()). This fixes #2202 and #2207

Old:
image

New:
image

Contribution Checklist:

  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Austen Adler added 2 commits May 4, 2024 00:37
`json-bigint` parses large numbers as bigints, but cannot output the same format.
Numbers with around 20 digits are converted to strings, and numbers around 45 digits are converted to scientific notation with a loss of precision
@leoferreiralima
Copy link

Nice!

@benjibuiltit
Copy link

Would love to see this one merge in.

@ifsheldon
Copy link

I encountered this as well. But somehow this PR is still not merged.

@Its-treason
Copy link
Member

Its-treason commented May 23, 2024

Parsing the request to JSON is still required, because the axiosRequest (Returned as request here) is passed into runPreRequest and the body is used inside scripts. Where the body is expected to be an object (Instead of a string with JSON) when it's valid JSON.

I would suggest replacing json-bigint with lossless-json using a parse like this:

const { parse, LosslessNumber } = require('lossless-json');

parse(bodyData, null, (value) => {
  // This function will always be called, when lossless-json encounters a number
  // By Default this function only does "return new LosslessNumber(value);", but this sometimes auses problems
  // Convert the Lossless number into whatever fits best e.g. Number / BigInt
  return new LosslessNumber(value).valueOf();
});

@austenadler
Copy link
Author

austenadler commented May 30, 2024

It sounds like this change might require some extra thinking. I think axios is the issue here. No matter how it's encoded, it's wrong.

Using LosslessNumber(value).valueOf() gives Do not know how to serialize a BigInt

And just LosslessNumber(value) does not serialize properly:
image

With this patch

diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js
index 855c2b60..dc2942e6 100644
--- a/packages/bruno-cli/src/runner/prepare-request.js
+++ b/packages/bruno-cli/src/runner/prepare-request.js
@@ -1,4 +1,5 @@
 const { get, each, filter } = require('lodash');
+const { parse, LosslessNumber } = require('lossless-json');
 const fs = require('fs');
 const decomment = require('decomment');
 
@@ -75,7 +76,9 @@ const prepareRequest = (request, collectionRoot) => {
     if (!contentTypeDefined) {
       axiosRequest.headers['content-type'] = 'application/json';
     }
-    axiosRequest.data = decomment(request.body.json);
+    axiosRequest.data = parse(decomment(request.body.json), null, (value) => {
+      return new LosslessNumber(value);
+    });
   }
 
   if (request.body.mode === 'text') {
diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js
index 98a0d42d..cf8dd5c3 100644
--- a/packages/bruno-electron/src/ipc/network/prepare-request.js
+++ b/packages/bruno-electron/src/ipc/network/prepare-request.js
@@ -1,5 +1,6 @@
 const { get, each, filter, extend } = require('lodash');
 const decomment = require('decomment');
+const { parse, LosslessNumber } = require('lossless-json');
 const FormData = require('form-data');
 const fs = require('fs');
 const path = require('path');
@@ -169,7 +170,9 @@ const prepareRequest = (request, collectionRoot, collectionPath) => {
     if (!contentTypeDefined) {
       axiosRequest.headers['content-type'] = 'application/json';
     }
-    axiosRequest.data = decomment(request.body.json);
+    axiosRequest.data = parse(decomment(request.body.json), null, (value) => {
+      return new LosslessNumber(value);
+    });
   }
 
   if (request.body.mode === 'text') {

Axios users have discussed this in axios/axios#4846, axios/axios#3440, and here. @Its-treason, what do you think of something like (doesn't quite work yet):

// From https://github.com/axios/axios/blob/4f189ec80ce01a0275d87d24463ef12b16715d9b/lib/defaults.js#L31-L55
request["transformResponse"] = [(data) => {
  if (typeof data === 'string') {
    try {
        data = losslessJson.parse(data);
    } catch (e) { /* Ignore */ }
  }
  return data;
}];
request["transformRequest"] = [(data) => {
  return losslessJson.stringify(data);
}, ...axios.defaults.transformRequest];

After this:

const axiosInstance = await configureRequest(
collectionUid,
request,
envVars,
collectionVariables,
processEnvVars,
collectionPath
);

@austenadler
Copy link
Author

After fighting with Axios, which seems to greedily try converting everything to JSON, I will wait and see what comes of axios/axios#6279.

If they allow us to pass a custom parser, we will just pass lossless-json's parse/stringify and everything will work. The only other alternative I see is to reimplement transformRequest, and pull in everything they don't export like utils, helpers/*, etc. I tried that but it's just too much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Long numbers (integers and floats) converted to strings
5 participants