diff --git a/packages/grafana-data/src/utils/object.ts b/packages/grafana-data/src/utils/object.ts index 0f40f8e636a..50d3ff25507 100644 --- a/packages/grafana-data/src/utils/object.ts +++ b/packages/grafana-data/src/utils/object.ts @@ -10,3 +10,22 @@ export const isEmptyObject = (value: unknown): value is Record => { return typeof value === 'object' && value !== null && Object.keys(value).length === 0; }; + +/** Stringifies an object that may contain circular references */ +export function safeStringifyValue(value: unknown) { + const getCircularReplacer = () => { + const seen = new WeakSet(); + return (_: string, value: object | null) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) { + return; + } + seen.add(value); + } + + return value; + }; + }; + + return JSON.stringify(value, getCircularReplacer()); +} diff --git a/public/app/core/utils/fetch.ts b/public/app/core/utils/fetch.ts index 49253f74a31..8b4031123b7 100644 --- a/public/app/core/utils/fetch.ts +++ b/public/app/core/utils/fetch.ts @@ -1,6 +1,6 @@ import { omitBy } from 'lodash'; -import { deprecationWarning } from '@grafana/data'; +import { deprecationWarning, safeStringifyValue } from '@grafana/data'; import { BackendSrvRequest } from '@grafana/runtime'; export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit => { @@ -93,7 +93,7 @@ export const parseBody = (options: BackendSrvRequest, isAppJson: boolean) => { return options.data; } - return isAppJson ? JSON.stringify(options.data) : new URLSearchParams(options.data); + return isAppJson ? safeStringifyValue(options.data) : new URLSearchParams(options.data); }; export async function parseResponseBody(