affaan-m/ECC · error

Invalid : expected JSON-compatible data

Error message

Invalid ${label}: expected JSON-compatible data

What it means

After handling undefined and strings, parseJsonLikeValue accepts null, arrays, plain objects, numbers, and booleans as JSON-compatible values. Any other JavaScript type (function, symbol, bigint, class instance, Date) cannot be safely serialized into the managed settings file, so the library throws. The message is generic; the label used by callers identifies the offending operation field.

Solutions

  1. Convert the offending value to a plain JSON-compatible structure before passing it (e.g. date.toISOString(), Object.fromEntries(map), plain object literal).
  2. Ensure operation objects are built from JSON.parse/structuredClone-safe data, not class instances.
  3. Check the value named by the error label and wrap it with a small normalize step that strips or converts non-JSON types recursively.
  4. If a Date-like value was intended, store it as an ISO 8601 string.

Example fix

// before
{ kind: 'update-claude-settings', payload: { updatedAt: new Date() } }

// after
{ kind: 'update-claude-settings', payload: { updatedAt: new Date().toISOString() } }
Defensive patterns

Strategy: type-guard

Validate before calling

function assertJsonCompatible(value) {
  if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return;
  if (Array.isArray(value)) return value.forEach(assertJsonCompatible);
  if (value && typeof value === 'object' && value.constructor === Object) {
    return Object.values(value).forEach(assertJsonCompatible);
  }
  throw new Error('Non-JSON value in payload');
}

Type guard

const isPlainJsonValue = v =>
  v === null || ['string', 'number', 'boolean'].includes(typeof v) ||
  (Array.isArray(v) && v.every(isPlainJsonValue)) ||
  (v !== null && typeof v === 'object' && v.constructor === Object && Object.values(v).every(isPlainJsonValue));

Try / catch

try {
  await applyOperation(op);
} catch (err) {
  if (/expected JSON-compatible data/.test(err.message)) {
    console.error('Convert Dates/Maps/class instances to plain JSON before building the operation.');
  } else throw err;
}

Prevention

When it happens

Trigger: getOperationJsonPayload or getOperationPreviousJson encounters a non-undefined value at mergePayload/managedPayload/payload/value/expectedValue or previousValue/previousJson/originalValue that is not a string, null, array, plain object, number, or boolean — e.g. a Date, Map, Set, class instance (like a custom config object), or function passed into the operation programmatically.

Common situations: Programmatically building install operations in a script and passing a Date or config-class instance where a plain JSON value is required; state loaded through a deserializer that revives values into class instances instead of plain objects.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/02cc64179100fd4a. Report an issue: GitHub.

Appendix: source

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

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 8321021c54)