overleaf/overleaf · error

Unsupported operation in EditOperationBuilder.fromJSON

Error message

Unsupported operation in EditOperationBuilder.fromJSON

What it means

EditOperationBuilder.fromJSON dispatches a raw JSON object to the correct EditOperation subclass via a chain of type-predicate checks (isRawSetCommentStateOperation, isRawEditNoOperation, etc.). If none of the predicates match, the raw object is not a recognized edit operation, so the builder throws rather than guessing. This guards the deserialization boundary against malformed or unknown payloads.

Source

Thrown at libraries/overleaf-editor-core/lib/operation/edit_operation_builder.js:37

   * @returns {EditOperation}
   */
  static fromJSON(raw) {
    if (isTextOperation(raw)) {
      return TextOperation.fromJSON(raw)
    }
    if (isRawAddCommentOperation(raw)) {
      return AddCommentOperation.fromJSON(raw)
    }
    if (isRawDeleteCommentOperation(raw)) {
      return DeleteCommentOperation.fromJSON(raw)
    }
    if (isRawSetCommentStateOperation(raw)) {
      return SetCommentStateOperation.fromJSON(raw)
    }
    if (isRawEditNoOperation(raw)) {
      return EditNoOperation.fromJSON()
    }
    throw new Error('Unsupported operation in EditOperationBuilder.fromJSON')
  }

  /**
   * @param {unknown} raw
   * @return {raw is RawEditOperation}
   */
  static isValid(raw) {
    return (
      isTextOperation(raw) ||
      isRawAddCommentOperation(raw) ||
      isRawDeleteCommentOperation(raw) ||
      isRawSetCommentStateOperation(raw) ||
      isRawEditNoOperation(raw)
    )
  }
}

/**

View on GitHub (pinned to 28ad3b03b7)

Solutions

  1. Inspect the raw object (console.log/JSON.stringify) and compare it against the isRaw*Operation predicates in edit_operation_builder.js to see which discriminator field is missing or wrong.
  2. Validate operation payloads at the ingestion boundary with a schema validator (e.g. zod/ajv) before calling fromJSON.
  3. Check library version compatibility between the producer of the JSON and this parser; upgrade or migrate old persisted ops.
  4. Wrap fromJSON in try/catch and quarantine the unrecognized op instead of crashing the whole deserialization pass.

Example fix

// before
const op = EditOperationBuilder.fromJSON(untrustedJson)
// after
if (!isKnownRawOperation(untrustedJson)) {
  throw new ValidationError('unknown edit operation: ' + JSON.stringify(untrustedJson))
}
const op = EditOperationBuilder.fromJSON(untrustedJson)
Defensive patterns

Strategy: type-guard

Validate before calling

const KNOWN_RAW_KEYS = ['setCommentState', 'editNo' /* match isRaw* predicates in edit_operation_builder.js */]
function isKnownRawOperation(raw) {
  return raw != null && typeof raw === 'object' && KNOWN_RAW_KEYS.some(k => k in raw)
}

Type guard

function isRawEditOperation(raw) {
  return typeof raw === 'object' && raw !== null &&
    (isRawSetCommentStateOperation(raw) || isRawEditNoOperation(raw))
}

Try / catch

let op
try {
  op = EditOperationBuilder.fromJSON(raw)
} catch (e) {
  if (e.message.startsWith('Unsupported operation')) {
    log.warn('skipping unknown edit op', raw)
    op = null
  } else throw e
}

Prevention

When it happens

Trigger: Calling EditOperationBuilder.fromJSON(raw) where raw is an object not matching any known raw-operation shape: wrong/misspelled discriminator property, null, a string, a number, or an operation type from a newer/older library version.

Common situations: Loading persisted history written by a different library version whose schema changed; receiving operations from an untrusted client or an API with validation gaps; hand-written JSON in tests that omits required fields.

Related errors


AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03). Data as JSON: /api/errors/3f50eb293dd263cb. Report an issue: GitHub.