apache/superset · error

Invalid JSON string: ${String(error)}

Error message

Invalid JSON string: ${String(error)}

What it means

The parseJson Handlebars helper (Handlebars chart plugin) wraps JSON.parse and rethrows with a prefixed message when the template passes a string that is not valid JSON. The original parse error text is preserved so the offending position in the string is visible.

Source

Thrown at superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx:113

  'formatNumber',
  function (number: any, locale = 'en-US') {
    if (typeof number !== 'number') {
      return number;
    }
    return number.toLocaleString(locale);
  },
);

// usage: {{parseJson jsonString}}
Handlebars.registerHelper('parseJson', (jsonString: string) => {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    if (error instanceof Error) {
      error.message = `Invalid JSON string: ${error.message}`;
      throw error;
    }
    throw new Error(`Invalid JSON string: ${String(error)}`);
  }
});

Helpers.registerHelpers(Handlebars);
HandlebarsGroupBy.register(Handlebars);

// `just-handlebars-helpers` registers a `formatDate` helper that lazily
// resolves `moment` via `global.moment` / `require('moment/min/moment-with-locales')`.
// The bundled viewer switched to dayjs and never satisfies that lookup, so the
// original helper throws "... is not a function" (see #32960). Re-register a
// dayjs-backed `formatDate` with the same `{{formatDate formatString date [locale]}}`
// signature so existing templates keep rendering.
Handlebars.registerHelper('formatDate', (formatString, date, localeString) => {
  const format = typeof formatString === 'string' ? formatString : '';
  const instance = dayjs(date ?? new Date());
  // Handlebars always passes its options object as the final argument, so a
  // locale is only present when the caller supplied an explicit string.
  // Note: `extendedDayjs` only loads the `en` locale, so passing a non-English

View on GitHub (pinned to f4587218dd)

Solutions

  1. Validate/clean the JSON column at the data source (CHECK constraints, backfill repairs).
  2. Guard in the template: {{#if jsonString}}{{parseJson jsonString}}{{/if}} to skip nulls.
  3. If the driver already returns objects, remove the parseJson call and use the field directly.
  4. Inspect the failing row in SQL Lab to find the exact malformed value and position from the message.

Example fix

{{! before }}
{{parseJson data.meta}}

{{! after }}
{{#if data.meta}}
  {{#if (isString data.meta)}}{{parseJson data.meta}}{{else}}{{stringify data.meta}}{{/if}}
{{/if}}
Defensive patterns

Strategy: validation

Validate before calling

function isJsonString(v: unknown): v is string {
  if (typeof v !== 'string') return false;
  try {
    JSON.parse(v);
    return true;
  } catch {
    return false;
  }
}

Type guard

function isParsableJson(v: unknown): v is string {
  return typeof v === 'string' && (() => { try { JSON.parse(v); return true; } catch { return false; } })();
}

Try / catch

{{#if (isJsonString data.meta)}}
  {{parseJson data.meta}}
{{else}}
  {{data.meta}}
{{/if}}

Prevention

When it happens

Trigger: Template uses {{parseJson jsonString}} where jsonString is a column containing malformed JSON, a number, null, or a value already parsed to an object (stringified twice / not stringified at all).

Common situations: Database column stores truncated or hand-edited JSON; NULL values reaching the helper; column is JSON-typed and the driver already returns an object, so JSON.parse receives '[object Object]'.

Understand the failure class

Related errors


AI-assisted analysis of apache/superset@f4587218dd (2026-08-14). Data as JSON: /api/errors/9e97ed0ba7adfcbd. Report an issue: GitHub.