Skip to content

Commit d3c5bf4

Browse files
committed
fix: handle prettifying json data with bruno variables
1 parent 68b2625 commit d3c5bf4

File tree

6 files changed

+122
-8
lines changed

6 files changed

+122
-8
lines changed

packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { humanizeRequestBodyMode } from 'utils/collections';
88
import StyledWrapper from './StyledWrapper';
99
import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index';
1010
import { toastError } from 'utils/common/error';
11-
import fastJsonFormat from 'fast-json-format';
11+
import { prettifyJsonString } from 'utils/common/index';
1212
import xmlFormat from 'xml-formatter';
1313

1414
const RequestBodyMode = ({ item, collection }) => {
@@ -39,7 +39,7 @@ const RequestBodyMode = ({ item, collection }) => {
3939
const onPrettify = () => {
4040
if (body?.json && bodyMode === 'json') {
4141
try {
42-
const prettyBodyJson = fastJsonFormat(body.json);
42+
const prettyBodyJson = prettifyJsonString(body.json);
4343
dispatch(
4444
updateRequestBody({
4545
content: prettyBodyJson,

packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import React, { useState } from 'react';
1010
import { useDispatch, useSelector } from 'react-redux';
1111
import { autoDetectLang } from 'utils/codemirror/lang-detect';
1212
import { toastError } from 'utils/common/error';
13-
import fastJsonFormat from 'fast-json-format';
13+
import { prettifyJsonString } from 'utils/common/index';
1414
import xmlFormat from 'xml-formatter';
1515
import WSRequestBodyMode from '../BodyMode/index';
1616

@@ -105,7 +105,7 @@ export const SingleWSMessage = ({
105105
const onPrettify = () => {
106106
if (codeType === 'json') {
107107
try {
108-
const prettyBodyJson = fastJsonFormat(content);
108+
const prettyBodyJson = prettifyJsonString(content);
109109
const currentMessages = [...(body.ws || [])];
110110
currentMessages[index] = {
111111
...currentMessages[index],

packages/bruno-app/src/utils/common/index.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { customAlphabet } from 'nanoid';
22
import xmlFormat from 'xml-formatter';
33
import { JSONPath } from 'jsonpath-plus';
44
import fastJsonFormat from 'fast-json-format';
5+
import { patternHasher } from '@usebruno/common/utils';
56

67
// a customized version of nanoid without using _ and -
78
export const uuid = () => {
@@ -293,7 +294,7 @@ export const formatResponse = (data, dataBufferString, mode, filter, bufferThres
293294
}
294295

295296
try {
296-
return fastJsonFormat(rawData);
297+
return prettifyJsonString(rawData);
297298
} catch (error) {}
298299

299300
if (typeof data === 'string') {
@@ -322,3 +323,17 @@ export const formatResponse = (data, dataBufferString, mode, filter, bufferThres
322323

323324
return safeStringifyJSON(data, !isVeryLargeResponse);
324325
};
326+
327+
export const prettifyJsonString = (jsonDataString) => {
328+
if (typeof jsonDataString !== 'string') return jsonDataString;
329+
try {
330+
const { hashed, restore } = patternHasher(jsonDataString);
331+
const formattedJsonDataStringHashed = fastJsonFormat(hashed);
332+
const formattedJsonDataString = restore(formattedJsonDataStringHashed);
333+
return formattedJsonDataString;
334+
} catch (error) {
335+
console.log('error formatting json data!');
336+
console.error(error);
337+
}
338+
return jsonDataString;
339+
};

packages/bruno-app/src/utils/common/index.spec.js

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
humanizeDate,
77
relativeDate,
88
getContentType,
9-
formatSize
9+
formatSize,
10+
prettifyJsonString
1011
} from './index';
1112

1213
describe('common utils', () => {
@@ -191,4 +192,98 @@ describe('common utils', () => {
191192
expect(formatSize(NaN)).toBe('0B');
192193
});
193194
});
195+
196+
describe('prettifyJsonString', () => {
197+
test('should return non-string inputs unchanged', () => {
198+
expect(prettifyJsonString(null)).toBe(null);
199+
expect(prettifyJsonString(undefined)).toBe(undefined);
200+
expect(prettifyJsonString(123)).toBe(123);
201+
expect(prettifyJsonString([])).toEqual([]);
202+
expect(prettifyJsonString({})).toEqual({});
203+
expect(prettifyJsonString(true)).toBe(true);
204+
});
205+
206+
test('should format valid JSON without Bruno variables', () => {
207+
const input = '{"name":"John","age":30}';
208+
const expected = `{\n "name": "John",\n "age": 30\n}`;
209+
console.log(prettifyJsonString(input));
210+
expect(prettifyJsonString(input)).toBe(expected);
211+
});
212+
213+
test('should format valid JSON with Bruno variables', () => {
214+
const input = '{"name": {{userName}}}';
215+
const expected = `{\n "name": {{userName}}\n}`;
216+
console.log(prettifyJsonString(input));
217+
expect(prettifyJsonString(input)).toBe(expected);
218+
});
219+
220+
test('should format complex json string', () => {
221+
const input = `{"bruno-variables": {{brunovariable}},"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 '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 'quotes' and 'code' snippets "}`;
222+
const expectedOutput = `{
223+
"bruno-variables": {{brunovariable}},
224+
"id": 123456789123456789123456789,
225+
"name": "Test 'JSON' Data with "quotes" — Pretty Print ",
226+
"active": true,
227+
"price": 199.9999999,
228+
"decimals": 1.00,
229+
"nullValue": null,
230+
"unicodeText": "こんにちは世界 ",
231+
"escapedCharacters": "Line1\nLine2\tTabbed\"Quoted\" and 'single quoted' with 'code' style",
232+
"nestedObject": {
233+
"level1": {
234+
"level2": {
235+
"emptyArray": [],
236+
"specialChars": "@#$%^&*()_+-=[]{}|;':,./<>?~",
237+
"booleanValues": [
238+
true,
239+
false,
240+
true
241+
],
242+
"numbers": [
243+
0,
244+
-1,
245+
1.23e10,
246+
3.1415926535
247+
]
248+
}
249+
}
250+
},
251+
"mixedArray": [
252+
"string with 'apostrophe'",
253+
42,
254+
false,
255+
null,
256+
{
257+
"innerObj": {
258+
"keyWithQuotes": "value containing 'single quotes'",
259+
"nestedArray": [
260+
{
261+
"a": "O'Reilly"
262+
}{
263+
"b": "'inline code'"
264+
},
265+
[
266+
"deep",
267+
"array",
268+
{
269+
"c": "contains 'quotes'"
270+
}
271+
]
272+
]
273+
}
274+
}
275+
],
276+
"nonStringVariable": {{nonStringVar}},
277+
"withBrunoVariable": "{{string}} '{{with}}' "{{variety}}" of '{{variables}}'",
278+
"dateExample": "2025-11-07T12:34:56Z",
279+
"regexExample": "^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$",
280+
"urls": {
281+
"website": "https://example.com?param='value'&flag='true'",
282+
"escapedURL": "https:\/\/escaped-url.com\/path\?q='search'\&debug='on'"
283+
},
284+
"multiLineString": "This is a long text\nthat spans multiple\nlines with 'quotes' and 'code' snippets "
285+
}`;
286+
expect(prettifyJsonString(input)).toBe(expectedOutput);
287+
});
288+
});
194289
});

packages/bruno-app/src/utils/curl/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { forOwn } from 'lodash';
22
import curlToJson from './curl-to-json';
3-
import fastJsonFormat from 'fast-json-format';
3+
import { prettifyJsonString } from 'utils/common/index';
44

55
export const getRequestFromCurlCommand = (curlCommand, requestType = 'http-request') => {
66
const parseFormData = (parsedBody) => {
@@ -67,7 +67,7 @@ export const getRequestFromCurlCommand = (curlCommand, requestType = 'http-reque
6767
body.file = parsedBody;
6868
}else if (contentType.includes('application/json')) {
6969
body.mode = 'json';
70-
body.json = fastJsonFormat(parsedBody);
70+
body.json = prettifyJsonString(parsedBody);
7171
} else if (contentType.includes('xml')) {
7272
body.mode = 'xml';
7373
body.xml = parsedBody;

packages/bruno-common/src/utils/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ export {
77
export {
88
buildFormUrlEncodedPayload
99
} from './form-data';
10+
11+
export {
12+
patternHasher
13+
} from './template-hasher';

0 commit comments

Comments
 (0)