karatelabs/karate · error · JsErrorException

cannot destructure

Error message

cannot destructure ${source}

What it means

Per ECMAScript 13.3.3.5, destructuring null or undefined throws a TypeError; Interpreter.destructurePattern raises this with the offending value in the message. Array patterns would call GetIterator and object patterns RequireObjectCoercible — both impossible for null/undefined — so the engine fails early with a clear message instead.

Solutions

  1. Default the source: `var {a} = maybeNull || {}` or `var [x] = maybeArr || []`
  2. Guard with a null check before destructuring
  3. Fix the upstream producer so it returns a real object/array
  4. Use karate's safe navigation / isNull checks before the destructuring statement

Example fix

// before
var { name, age } = response.user;
// after
var { name, age } = response.user || {};
Defensive patterns

Strategy: validation

Validate before calling

const src = response.user || {};
var { name, age } = src;

Type guard

function isDestructurable(x) { return x != null && (typeof x === 'object' || typeof x[Symbol.iterator] === 'function'); }

Try / catch

try { var { a } = src; } catch (e) { if (String(e).includes('cannot destructure')) { /* fall back to defaults */ } else { throw e; } }

Prevention

When it happens

Trigger: `var {a, b} = someVar` or `var [x, y] = someVar` where someVar is null or undefined — commonly an unset karate variable, a missing JSON path (`response.data` when absent), or a function returning undefined.

Common situations: Destructuring an API response field that is absent; karate variable never set before the script runs; optional chaining omitted (`res.user` is undefined when user is missing); schema drift where a previously-present field disappears.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/5f582149233e6120. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/Interpreter.java:275

    }

    /**
     * Walk a destructuring pattern (LIT_ARRAY or LIT_OBJECT) and bind each leaf
     * target from the corresponding piece of `source`. When `bindScope` is
     * non-null the pattern is a declaration (`var/let/const [a] = ...`) and
     * leaves are declared; when null it is an assignment expression
     * (`[a] = ...`) and leaves are updated / applied via `PropertyAccess.set`.
     * Nested patterns recurse; default values fire only when the corresponding
     * source value is undefined.
     */
    @SuppressWarnings("unchecked")
    private static void destructurePattern(Node pattern, CoreContext context,
                                            BindScope bindScope, Object source, boolean initialized) {
        // Per spec 13.3.3.5: destructuring null/undefined throws TypeError.
        // ArrayBindingPattern calls GetIterator(value); ObjectBindingPattern
        // calls RequireObjectCoercible(value); both fail for null/undefined.
        if (source == null || source == Terms.UNDEFINED) {
            throw JsErrorException.typeError("cannot destructure " + source);
        }
        if (pattern.type == NodeType.LIT_ARRAY) {
            // Array destructuring per spec 13.3.3.5 calls GetIterator(value).
            // Pull through the unified iterator surface — IterUtils throws TypeError
            // for non-iterables (boolean, plain object without @@iterator, etc.).
            JsIterator iter = IterUtils.getIterator(source, context);
            int last = pattern.size() - 1;
            // Per spec 13.15.5.3 / 8.5.2: when the pattern finishes with the iterator
            // not yet exhausted (no rest element, or a rest never reached), perform
            // IteratorClose(iterator). A rest element drives next() to done, so close
            // then no-ops. On an abrupt completion (a binding / default expr throws),
            // IteratorClose still runs but the original throw wins.
            boolean abrupt = false;
            try {
                for (int i = 1; i < last; i++) {
                    Node elem = pattern.get(i);
                    Node first = elem.get(0);
                    if (first.isToken() && first.token.type == DOT_DOT_DOT) {

View on GitHub (pinned to a22eb90246)