diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js index 0a7fd98c9..6ab530ebd 100644 --- a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js +++ b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js @@ -6,9 +6,9 @@ import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/colle import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { useTheme } from 'providers/Theme'; import StyledWrapper from './StyledWrapper'; -import { format, applyEdits } from 'jsonc-parser'; import { IconWand } from '@tabler/icons'; import toast from 'react-hot-toast'; +import { prettifyJsonString } from 'utils/common/index'; const GraphQLVariables = ({ variables, item, collection }) => { const dispatch = useDispatch(); @@ -19,8 +19,7 @@ const GraphQLVariables = ({ variables, item, collection }) => { const onPrettify = () => { if (!variables) return; try { - const edits = format(variables, undefined, { tabSize: 2, insertSpaces: true }); - const prettyVariables = applyEdits(variables, edits); + const prettyVariables = prettifyJsonString(variables); dispatch( updateRequestGraphqlVariables({ variables: prettyVariables, diff --git a/packages/bruno-app/src/components/RequestPane/GrpcBody/index.js b/packages/bruno-app/src/components/RequestPane/GrpcBody/index.js index e17bdb074..0e1eb8d0b 100644 --- a/packages/bruno-app/src/components/RequestPane/GrpcBody/index.js +++ b/packages/bruno-app/src/components/RequestPane/GrpcBody/index.js @@ -12,9 +12,9 @@ import StyledWrapper from './StyledWrapper'; import { IconSend, IconRefresh, IconWand, IconPlus, IconTrash, IconChevronDown, IconChevronUp } from '@tabler/icons'; import ToolHint from 'components/ToolHint/index'; import { toastError } from 'utils/common/error'; -import { format, applyEdits } from 'jsonc-parser'; import toast from 'react-hot-toast' import { getAbsoluteFilePath } from 'utils/common/path'; +import { prettifyJsonString } from 'utils/common/index'; const SingleGrpcMessage = ({ message, item, collection, index, methodType, isCollapsed, onToggleCollapse, handleRun, canClientSendMultipleMessages }) => { const dispatch = useDispatch(); @@ -130,8 +130,7 @@ const SingleGrpcMessage = ({ message, item, collection, index, methodType, isCol const onPrettify = () => { try { - const edits = format(content, undefined, { tabSize: 2, insertSpaces: true }); - const prettyBodyJson = applyEdits(content, edits); + const prettyBodyJson = prettifyJsonString(content); const currentMessages = [...(body.grpc || [])]; currentMessages[index] = { diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index 8b191b0a6..e94fd8be6 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 { format, applyEdits } from 'jsonc-parser'; import { patternHasher } from '@usebruno/common/utils'; // a customized version of nanoid without using _ and - @@ -294,7 +295,7 @@ export const formatResponse = (data, dataBufferString, mode, filter, bufferThres } try { - return prettifyJsonString(rawData); + return fastJsonFormat(rawData); } catch (error) {} if (typeof data === 'string') { @@ -326,9 +327,11 @@ export const formatResponse = (data, dataBufferString, mode, filter, bufferThres export const prettifyJsonString = (jsonDataString) => { if (typeof jsonDataString !== 'string') return jsonDataString; + try { const { hashed, restore } = patternHasher(jsonDataString); - const formattedJsonDataStringHashed = fastJsonFormat(hashed); + const edits = format(hashed, undefined, { tabSize: 2, insertSpaces: true }); + const formattedJsonDataStringHashed = applyEdits(hashed, edits); const formattedJsonDataString = restore(formattedJsonDataStringHashed); return formattedJsonDataString; } catch (error) { diff --git a/packages/bruno-app/src/utils/common/index.spec.js b/packages/bruno-app/src/utils/common/index.spec.js index 8ed85932e..55958b954 100644 --- a/packages/bruno-app/src/utils/common/index.spec.js +++ b/packages/bruno-app/src/utils/common/index.spec.js @@ -218,16 +218,16 @@ describe('common utils', () => { }); 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 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 ", + "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", + "escapedCharacters": "Line1\\nLine2\\tTabbed\"Quoted\" and 'single quoted' with 'code' style", "nestedObject": { "level1": { "level2": { @@ -280,7 +280,7 @@ describe('common utils', () => { "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 " + "multiLineString": "This is a long text\\nthat spans multiple\\nlines with \`backticks\` 'quotes' and 'code' snippets " }`; expect(prettifyJsonString(input)).toBe(expectedOutput); });