diff --git a/src/plugins/console/public/application/containers/editor/utils/requests_utils.test.ts b/src/plugins/console/public/application/containers/editor/utils/requests_utils.test.ts index 29c975300db6b..504afdadb6038 100644 --- a/src/plugins/console/public/application/containers/editor/utils/requests_utils.test.ts +++ b/src/plugins/console/public/application/containers/editor/utils/requests_utils.test.ts @@ -426,6 +426,17 @@ describe('requests_utils', () => { expect(request).toEqual({ method: 'GET', url: '_search', data: ['{\n "query": {}\n}'] }); }); + it('correctly handles nested braces', () => { + const content = ['GET _search', '{', ' "query": "{a} {b}"', '}', '{', ' "query": {}', '}']; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 7); + expect(request).toEqual({ + method: 'GET', + url: '_search', + data: ['{\n "query": "{a} {b}"\n}', '{\n "query": {}\n}'], + }); + }); + it('works for several request bodies', () => { const content = ['GET _search', '{', ' "query": {}', '}', '{', ' "query": {}', '}']; const model = getMockModel(content); diff --git a/src/plugins/console/public/application/containers/editor/utils/requests_utils.ts b/src/plugins/console/public/application/containers/editor/utils/requests_utils.ts index 123daf919cf22..628e111df7836 100644 --- a/src/plugins/console/public/application/containers/editor/utils/requests_utils.ts +++ b/src/plugins/console/public/application/containers/editor/utils/requests_utils.ts @@ -271,23 +271,61 @@ const replaceVariables = ( return text; }; +/** + * Splits a concatenated string of JSON objects into individual JSON objects. + * + * This function takes a string containing one or more JSON objects concatenated together, + * separated by optional whitespace, and splits them into an array of individual JSON strings. + * It ensures that nested objects and strings containing braces do not interfere with the splitting logic. + * + * Example inputs: + * - '{ "query": "test"} { "query": "test" }' -> ['{ "query": "test"}', '{ "query": "test" }'] + * - '{ "query": "test"}' -> ['{ "query": "test"}'] + * - '{ "query": "{a} {b}"}' -> ['{ "query": "{a} {b}"}'] + * + */ const splitDataIntoJsonObjects = (dataString: string): string[] => { - const jsonSplitRegex = /}\s*{/; - if (dataString.match(jsonSplitRegex)) { - return dataString.split(jsonSplitRegex).map((part, index, parts) => { - let restoredBracketsString = part; - // add an opening bracket to all parts except the 1st - if (index > 0) { - restoredBracketsString = `{${restoredBracketsString}`; + const jsonObjects = []; + // Tracks the depth of nested braces + let depth = 0; + // Holds the current JSON object as we iterate + let currentObject = ''; + // Tracks whether the current position is inside a string + let insideString = false; + + // Iterate through each character in the input string + for (let i = 0; i < dataString.length; i++) { + const char = dataString[i]; + // Append the character to the current JSON object string + currentObject += char; + + // If the character is a double quote and it is not escaped, toggle the `insideString` state + if (char === '"' && dataString[i - 1] !== '\\') { + insideString = !insideString; + } else if (!insideString) { + // Only modify depth if not inside a string + + if (char === '{') { + depth++; + } else if (char === '}') { + depth--; } - // add a closing bracket to all parts except the last - if (index < parts.length - 1) { - restoredBracketsString = `${restoredBracketsString}}`; + + // If depth is zero, we have completed a JSON object + if (depth === 0) { + jsonObjects.push(currentObject.trim()); + currentObject = ''; } - return restoredBracketsString; - }); + } } - return [dataString]; + + // If there's remaining data in currentObject, add it as the last JSON object + if (currentObject.trim()) { + jsonObjects.push(currentObject.trim()); + } + + // Filter out any empty strings from the result array + return jsonObjects.filter((obj) => obj !== ''); }; const cleanUpWhitespaces = (line: string): string => {