affaan-m/ECC · error · Error

Invalid ${label}: expected JSON-compatible data

Error message

Invalid ${label}: expected JSON-compatible data

What it means

Thrown by parseJsonLikeValue() when the input value is not undefined, not a string, and not one of the JSON-compatible types (null, array, plain object, number, boolean). This catches values like functions, symbols, class instances, or NaN that cannot survive JSON round-tripping.

Source

Thrown at scripts/lib/install-lifecycle.js:170

function parseJsonLikeValue(value, label) {
  if (value === undefined) {
    return undefined;
  }

  if (typeof value === 'string') {
    try {
      return JSON.parse(value);
    } catch (error) {
      throw new Error(`Invalid ${label}: ${error.message}`);
    }
  }

  if (value === null || Array.isArray(value) || isPlainObject(value) || typeof value === 'number' || typeof value === 'boolean') {
    return cloneJsonValue(value);
  }

  throw new Error(`Invalid ${label}: expected JSON-compatible data`);
}

function getOperationTextContent(operation) {
  const candidateKeys = ['renderedContent', 'content', 'managedContent', 'expectedContent', 'templateOutput'];

  for (const key of candidateKeys) {
    if (typeof operation[key] === 'string') {
      return operation[key];
    }
  }

  return null;
}

function getOperationJsonPayload(operation) {
  const candidateKeys = ['mergePayload', 'managedPayload', 'payload', 'value', 'expectedValue'];

  for (const key of candidateKeys) {

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Convert non-JSON values before assigning: Date -> .toISOString(), Map -> Object.fromEntries(...), Set -> [...set], BigInt -> String(...).
  2. Strip functions/symbols from the payload before calling parseJsonLikeValue.
  3. Construct operation payloads as plain object literals with only JSON-compatible leaves.
  4. Run JSON.parse(JSON.stringify(payload)) yourself first to surface the bad field.

Example fix

// before
operation.mergePayload = new Map([['key', 'value']]);

// after
operation.mergePayload = Object.fromEntries(new Map([['key', 'value']]));
// or
operation.mergePayload = { key: 'value' };
Defensive patterns

Strategy: type-guard

Validate before calling

function isJsonCompatible(v) {
  if (v === null || typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return true;
  if (Array.isArray(v)) return v.every(isJsonCompatible);
  if (v && typeof v === 'object') return Object.values(v).every(isJsonCompatible);
  return false;
}
if (!isJsonCompatible(payload)) {
  throw new Error(`${label} contains non-JSON values (function/symbol/instance)`);
}

Type guard

function isJsonCompatible(v) {
  if (v === null) return true;
  if (typeof v === 'function' || typeof v === 'symbol' || typeof v === 'bigint') return false;
  if (typeof v !== 'object') return typeof v !== 'undefined';
  if (Array.isArray(v)) return v.every(isJsonCompatible);
  return Object.values(v).every(isJsonCompatible);
}

Try / catch

try {
  return parseJsonLikeValue(value, label);
} catch (err) {
  if (/expected JSON-compatible data/.test(err.message)) {
    throw new Error(`${label} must be plain JSON data; got ${Object.prototype.toString.call(value)}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: parseJsonLikeValue(value, label) where typeof value is 'function', 'symbol', 'bigint', or value is a class instance / Map / Set / Date without toJSON. None of the branches at install-lifecycle.js:158-167 match, so the final throw fires.

Common situations: Caller builds an operation from rich runtime objects (Map, Date, class instance) instead of plain data; a function reference leaked into a payload field; bigint returned from a BigInt-aggregate; NaN/undefined sneaks in via a number field.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/02cc64179100fd4a. Report an issue: GitHub.