fix: lossless json serialization

This commit is contained in:
Anoop M D 2024-08-07 13:11:58 +05:30
parent 72c3aaa5ba
commit 103e0a2444
14 changed files with 97 additions and 32 deletions

4
package-lock.json generated
View File

@ -19668,7 +19668,7 @@
}, },
"packages/bruno-electron": { "packages/bruno-electron": {
"name": "bruno", "name": "bruno",
"version": "v1.23.1", "version": "v1.24.0",
"dependencies": { "dependencies": {
"@aws-sdk/credential-providers": "3.525.0", "@aws-sdk/credential-providers": "3.525.0",
"@usebruno/common": "0.1.0", "@usebruno/common": "0.1.0",
@ -19698,6 +19698,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lossless-json": "^4.0.1",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"mustache": "^4.2.0", "mustache": "^4.2.0",
"nanoid": "3.3.4", "nanoid": "3.3.4",
@ -26347,6 +26348,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lossless-json": "^4.0.1",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"mustache": "^4.2.0", "mustache": "^4.2.0",
"nanoid": "3.3.4", "nanoid": "3.3.4",

View File

@ -19,7 +19,9 @@
"test": "jest" "test": "jest"
}, },
"jest": { "jest": {
"modulePaths": ["node_modules"] "modulePaths": [
"node_modules"
]
}, },
"dependencies": { "dependencies": {
"@aws-sdk/credential-providers": "3.525.0", "@aws-sdk/credential-providers": "3.525.0",
@ -50,6 +52,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lossless-json": "^4.0.1",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"mustache": "^4.2.0", "mustache": "^4.2.0",
"nanoid": "3.3.4", "nanoid": "3.3.4",

View File

@ -16,13 +16,21 @@ const {
sanitizeDirectoryName sanitizeDirectoryName
} = require('../utils/filesystem'); } = require('../utils/filesystem');
const { openCollectionDialog } = require('../app/collections'); const { openCollectionDialog } = require('../app/collections');
const { generateUidBasedOnHash, stringifyJson, safeParseJSON, safeStringifyJSON } = require('../utils/common'); const { generateUidBasedOnHash, safeParseJSON, safeStringifyJSON } = require('../utils/common');
const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids'); const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cookies'); const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cookies');
const EnvironmentSecretsStore = require('../store/env-secrets'); const EnvironmentSecretsStore = require('../store/env-secrets');
const environmentSecretsStore = new EnvironmentSecretsStore(); const environmentSecretsStore = new EnvironmentSecretsStore();
const stringifyJson = async (str) => {
try {
return JSON.stringify(str, null, 2);
} catch (err) {
return Promise.reject(err);
}
};
const envHasSecrets = (environment = {}) => { const envHasSecrets = (environment = {}) => {
const secrets = _.filter(environment.variables, (v) => v.secret); const secrets = _.filter(environment.variables, (v) => v.secret);

View File

@ -1,6 +1,7 @@
const URL = require('url'); const URL = require('url');
const Socket = require('net').Socket; const Socket = require('net').Socket;
const axios = require('axios'); const axios = require('axios');
const { safeStringifyJSON } = require('../../utils/common');
const connectionCache = new Map(); // Cache to store checkConnection() results const connectionCache = new Map(); // Cache to store checkConnection() results
const LOCAL_IPV6 = '::1'; const LOCAL_IPV6 = '::1';
@ -49,7 +50,22 @@ const checkConnection = (host, port) =>
*/ */
function makeAxiosInstance() { function makeAxiosInstance() {
/** @type {axios.AxiosInstance} */ /** @type {axios.AxiosInstance} */
const instance = axios.create(); const instance = axios.create({
transformRequest: function transformRequest(data, headers) {
const isObject = (thing) => thing !== null && typeof thing === 'object';
const hasJSONContentType = () => {
const contentType = headers?.['Content-Type'] || headers?.['content-type'] || '';
return contentType.includes('application/json');
};
if (isObject(data) && hasJSONContentType()) {
headers.setContentType('application/json', false);
return safeStringifyJSON(data);
}
axios.defaults.transformRequest.forEach((tr) => tr(data, headers));
}
});
instance.interceptors.request.use(async (config) => { instance.interceptors.request.use(async (config) => {
const url = URL.parse(config.url); const url = URL.parse(config.url);

View File

@ -16,7 +16,7 @@ const prepareRequest = require('./prepare-request');
const prepareCollectionRequest = require('./prepare-collection-request'); const prepareCollectionRequest = require('./prepare-collection-request');
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request'); const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token'); const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
const { uuid } = require('../../utils/common'); const { uuid, parseJson } = require('../../utils/common');
const interpolateVars = require('./interpolate-vars'); const interpolateVars = require('./interpolate-vars');
const { interpolateString } = require('./interpolate-string'); const { interpolateString } = require('./interpolate-string');
const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper');
@ -285,7 +285,7 @@ const parseDataFromResponse = (response) => {
// Filter out ZWNBSP character // Filter out ZWNBSP character
// https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d // https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d
data = data.replace(/^\uFEFF/, ''); data = data.replace(/^\uFEFF/, '');
data = JSON.parse(data); data = parseJson(data);
} catch {} } catch {}
return { data, dataBuffer }; return { data, dataBuffer };

View File

@ -1,5 +1,6 @@
const { interpolate } = require('@usebruno/common'); const { interpolate } = require('@usebruno/common');
const { each, forOwn, cloneDeep, find } = require('lodash'); const { each, forOwn, cloneDeep } = require('lodash');
const { parseJson, stringifyJson } = require('../../utils/common');
const getContentType = (headers = {}) => { const getContentType = (headers = {}) => {
let contentType = ''; let contentType = '';
@ -61,9 +62,9 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn
if (contentType.includes('json')) { if (contentType.includes('json')) {
if (typeof request.data === 'object') { if (typeof request.data === 'object') {
try { try {
let parsed = JSON.stringify(request.data); let parsed = stringifyJson(request.data);
parsed = _interpolate(parsed); parsed = _interpolate(parsed);
request.data = JSON.parse(parsed); request.data = parseJson(parsed);
} catch (err) {} } catch (err) {}
} }
@ -75,9 +76,9 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn
} else if (contentType === 'application/x-www-form-urlencoded') { } else if (contentType === 'application/x-www-form-urlencoded') {
if (typeof request.data === 'object') { if (typeof request.data === 'object') {
try { try {
let parsed = JSON.stringify(request.data); let parsed = stringifyJson(request.data);
parsed = _interpolate(parsed); parsed = _interpolate(parsed);
request.data = JSON.parse(parsed); request.data = parseJson(parsed);
} catch (err) {} } catch (err) {}
} }
} else { } else {

View File

@ -1,7 +1,7 @@
const os = require('os'); const os = require('os');
const { get, each, filter, extend, compact } = require('lodash'); const { get, each, filter, extend, compact } = require('lodash');
const decomment = require('decomment'); const decomment = require('decomment');
var JSONbig = require('json-bigint'); const { parseJson } = require('../../utils/common');
const FormData = require('form-data'); const FormData = require('form-data');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@ -349,7 +349,7 @@ const prepareRequest = (item, collection) => {
jsonBody = request?.body?.json; jsonBody = request?.body?.json;
} }
try { try {
axiosRequest.data = JSONbig.parse(jsonBody); axiosRequest.data = parseJson(jsonBody);
} catch (error) { } catch (error) {
axiosRequest.data = jsonBody; axiosRequest.data = jsonBody;
} }

View File

@ -1,4 +1,5 @@
const { customAlphabet } = require('nanoid'); const { customAlphabet } = require('nanoid');
const { stringify, parse, LosslessNumber } = require('lossless-json');
// a customized version of nanoid without using _ and - // a customized version of nanoid without using _ and -
const uuid = () => { const uuid = () => {
@ -9,25 +10,21 @@ const uuid = () => {
return customNanoId(); return customNanoId();
}; };
const stringifyJson = async (str) => { const stringifyJson = (data) => {
try { return stringify(data);
return JSON.stringify(str, null, 2);
} catch (err) {
return Promise.reject(err);
}
}; };
const parseJson = async (obj) => { const parseJson = (data) => {
try { return parse(data, null, (value) => {
return JSON.parse(obj); // By default, this will return the LosslessNumber object for big ints
} catch (err) { // need to convert it into a number
return Promise.reject(err); return new LosslessNumber(value).valueOf();
} });
}; };
const safeStringifyJSON = (data) => { const safeStringifyJSON = (data) => {
try { try {
return JSON.stringify(data); return stringify(data);
} catch (e) { } catch (e) {
return data; return data;
} }
@ -35,7 +32,11 @@ const safeStringifyJSON = (data) => {
const safeParseJSON = (data) => { const safeParseJSON = (data) => {
try { try {
return JSON.parse(data); return parse(data, null, (value) => {
// By default, this will return the LosslessNumber object for big ints
// need to convert it into a number
return new LosslessNumber(value).valueOf();
});
} catch (e) { } catch (e) {
return data; return data;
} }

View File

@ -0,0 +1,34 @@
meta {
name: echo bigint
type: http
seq: 6
}
post {
url: {{host}}/api/echo/json
body: json
auth: none
}
headers {
foo: bar
}
auth:basic {
username: asd
password: j
}
auth:bearer {
token:
}
body:json {
{
"hello": 990531470713421825
}
}
assert {
res.status: eq 200
}

View File

@ -1,7 +1,7 @@
meta { meta {
name: echo json name: echo json
type: http type: http
seq: 1 seq: 2
} }
post { post {

View File

@ -1,7 +1,7 @@
meta { meta {
name: echo plaintext name: echo plaintext
type: http type: http
seq: 2 seq: 3
} }
post { post {

View File

@ -1,7 +1,7 @@
meta { meta {
name: echo xml parsed name: echo xml parsed
type: http type: http
seq: 3 seq: 4
} }
post { post {

View File

@ -1,7 +1,7 @@
meta { meta {
name: echo xml raw name: echo xml raw
type: http type: http
seq: 4 seq: 5
} }
post { post {