diff --git a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js index 51c2ddbf9..e8240efc8 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js @@ -8,7 +8,7 @@ import { humanizeRequestBodyMode } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index'; import { toastError } from 'utils/common/error'; -import fastJsonFormat from 'fast-json-format'; +import { prettifyJsonString } from 'utils/common/index'; import xmlFormat from 'xml-formatter'; const RequestBodyMode = ({ item, collection }) => { @@ -39,7 +39,7 @@ const RequestBodyMode = ({ item, collection }) => { const onPrettify = () => { if (body?.json && bodyMode === 'json') { try { - const prettyBodyJson = fastJsonFormat(body.json); + const prettyBodyJson = prettifyJsonString(body.json); dispatch( updateRequestBody({ content: prettyBodyJson, diff --git a/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js b/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js index e22856a6c..2d56cf46a 100644 --- a/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js +++ b/packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js @@ -10,7 +10,7 @@ import React, { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { autoDetectLang } from 'utils/codemirror/lang-detect'; import { toastError } from 'utils/common/error'; -import fastJsonFormat from 'fast-json-format'; +import { prettifyJsonString } from 'utils/common/index'; import xmlFormat from 'xml-formatter'; import WSRequestBodyMode from '../BodyMode/index'; @@ -105,7 +105,7 @@ export const SingleWSMessage = ({ const onPrettify = () => { if (codeType === 'json') { try { - const prettyBodyJson = fastJsonFormat(content); + const prettyBodyJson = prettifyJsonString(content); const currentMessages = [...(body.ws || [])]; currentMessages[index] = { ...currentMessages[index], diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index 7a122f10e..8b191b0a6 100644 --- a/packages/bruno-app/src/utils/common/index.js +++ b/packages/bruno-app/src/utils/common/index.js @@ -2,6 +2,7 @@ import { customAlphabet } from 'nanoid'; import xmlFormat from 'xml-formatter'; import { JSONPath } from 'jsonpath-plus'; import fastJsonFormat from 'fast-json-format'; +import { patternHasher } from '@usebruno/common/utils'; // a customized version of nanoid without using _ and - export const uuid = () => { @@ -293,7 +294,7 @@ export const formatResponse = (data, dataBufferString, mode, filter, bufferThres } try { - return fastJsonFormat(rawData); + return prettifyJsonString(rawData); } catch (error) {} if (typeof data === 'string') { @@ -322,3 +323,17 @@ export const formatResponse = (data, dataBufferString, mode, filter, bufferThres return safeStringifyJSON(data, !isVeryLargeResponse); }; + +export const prettifyJsonString = (jsonDataString) => { + if (typeof jsonDataString !== 'string') return jsonDataString; + try { + const { hashed, restore } = patternHasher(jsonDataString); + const formattedJsonDataStringHashed = fastJsonFormat(hashed); + const formattedJsonDataString = restore(formattedJsonDataStringHashed); + return formattedJsonDataString; + } catch (error) { + console.log('error formatting json data!'); + console.error(error); + } + return jsonDataString; +}; diff --git a/packages/bruno-app/src/utils/common/index.spec.js b/packages/bruno-app/src/utils/common/index.spec.js index ba2fce87e..8ed85932e 100644 --- a/packages/bruno-app/src/utils/common/index.spec.js +++ b/packages/bruno-app/src/utils/common/index.spec.js @@ -6,7 +6,8 @@ import { humanizeDate, relativeDate, getContentType, - formatSize + formatSize, + prettifyJsonString } from './index'; describe('common utils', () => { @@ -191,4 +192,97 @@ describe('common utils', () => { expect(formatSize(NaN)).toBe('0B'); }); }); + + describe('prettifyJsonString', () => { + test('should return non-string inputs unchanged', () => { + expect(prettifyJsonString(null)).toBe(null); + expect(prettifyJsonString(undefined)).toBe(undefined); + expect(prettifyJsonString(123)).toBe(123); + expect(prettifyJsonString([])).toEqual([]); + expect(prettifyJsonString({})).toEqual({}); + expect(prettifyJsonString(true)).toBe(true); + }); + + test('should format valid JSON without Bruno variables', () => { + const input = '{"name":"John","age":30}'; + const expected = `{\n "name": "John",\n "age": 30\n}`; + console.log(prettifyJsonString(input)); + expect(prettifyJsonString(input)).toBe(expected); + }); + + test('should format valid JSON with Bruno variables', () => { + const input = '{"name": {{userName}}}'; + const expected = `{\n "name": {{userName}}\n}`; + console.log(prettifyJsonString(input)); + expect(prettifyJsonString(input)).toBe(expected); + }); + + test('should format complex json string', () => { + const input = `{"id": 123456789123456789123456789,"name": "Test 'JSON' Data with "quotes" — Pretty Print ","active": true,"price": 199.9999999,"decimals": 1.00,"nullValue": null,"unicodeText": "こんにちは世界 ","escapedCharacters": "Line1\nLine2\tTabbed\"Quoted\" and 'single quoted' with 'code' style","nestedObject": { "level1": { "level2": { "emptyArray": [], "specialChars": "@#$%^&*()_+-=[]{}|;':,./<>?~", "booleanValues": [ true, false, true ], "numbers": [ 0, -1, 1.23e10, 3.1415926535 ] } }},"mixedArray": [ "string with 'apostrophe'", 42, false, null, { "innerObj": { "keyWithQuotes": "value containing \`backticks\` and 'single quotes'", "nestedArray": [ { "a": "O'Reilly" }{ "b": "'inline code'" }, [ "deep", "array", { "c": "contains 'quotes'" } ] ] } }],"nonStringVariable": {{nonStringVar}},"withBrunoVariable": "{{string}} '{{with}}' "{{variety}}" of '{{variables}}'","dateExample": "2025-11-07T12:34:56Z","regexExample": "^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$","urls": { "website": "https://example.com?param='value'&flag='true'", "escapedURL": "https:\/\/escaped-url.com\/path\?q='search'\&debug='on'"},"multiLineString": "This is a long text\nthat spans multiple\nlines with \`backticks\` 'quotes' and 'code' snippets "}`; + const expectedOutput = `{ + "id": 123456789123456789123456789, + "name": "Test 'JSON' Data with "quotes" — Pretty Print ", + "active": true, + "price": 199.9999999, + "decimals": 1.00, + "nullValue": null, + "unicodeText": "こんにちは世界 ", + "escapedCharacters": "Line1\nLine2\tTabbed\"Quoted\" and 'single quoted' with 'code' style", + "nestedObject": { + "level1": { + "level2": { + "emptyArray": [], + "specialChars": "@#$%^&*()_+-=[]{}|;':,./<>?~", + "booleanValues": [ + true, + false, + true + ], + "numbers": [ + 0, + -1, + 1.23e10, + 3.1415926535 + ] + } + } + }, + "mixedArray": [ + "string with 'apostrophe'", + 42, + false, + null, + { + "innerObj": { + "keyWithQuotes": "value containing \`backticks\` and 'single quotes'", + "nestedArray": [ + { + "a": "O'Reilly" + }{ + "b": "'inline code'" + }, + [ + "deep", + "array", + { + "c": "contains 'quotes'" + } + ] + ] + } + } + ], + "nonStringVariable": {{nonStringVar}}, + "withBrunoVariable": "{{string}} '{{with}}' "{{variety}}" of '{{variables}}'", + "dateExample": "2025-11-07T12:34:56Z", + "regexExample": "^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$", + "urls": { + "website": "https://example.com?param='value'&flag='true'", + "escapedURL": "https:\/\/escaped-url.com\/path\?q='search'\&debug='on'" + }, + "multiLineString": "This is a long text\nthat spans multiple\nlines with \`backticks\` 'quotes' and 'code' snippets " +}`; + expect(prettifyJsonString(input)).toBe(expectedOutput); + }); + }); }); diff --git a/packages/bruno-app/src/utils/curl/index.js b/packages/bruno-app/src/utils/curl/index.js index cfb845945..3fa30a95f 100644 --- a/packages/bruno-app/src/utils/curl/index.js +++ b/packages/bruno-app/src/utils/curl/index.js @@ -1,6 +1,6 @@ import { forOwn } from 'lodash'; import curlToJson from './curl-to-json'; -import fastJsonFormat from 'fast-json-format'; +import { prettifyJsonString } from 'utils/common/index'; export const getRequestFromCurlCommand = (curlCommand, requestType = 'http-request') => { const parseFormData = (parsedBody) => { @@ -67,7 +67,7 @@ export const getRequestFromCurlCommand = (curlCommand, requestType = 'http-reque body.file = parsedBody; }else if (contentType.includes('application/json')) { body.mode = 'json'; - body.json = fastJsonFormat(parsedBody); + body.json = prettifyJsonString(parsedBody); } else if (contentType.includes('xml')) { body.mode = 'xml'; body.xml = parsedBody; diff --git a/packages/bruno-common/src/utils/index.ts b/packages/bruno-common/src/utils/index.ts index f20eab9a4..94e8cc2cd 100644 --- a/packages/bruno-common/src/utils/index.ts +++ b/packages/bruno-common/src/utils/index.ts @@ -7,3 +7,7 @@ export { export { buildFormUrlEncodedPayload } from './form-data'; + +export { + patternHasher +} from './template-hasher';