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
- Default the source: `var {a} = maybeNull || {}` or `var [x] = maybeArr || []`
- Guard with a null check before destructuring
- Fix the upstream producer so it returns a real object/array
- 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
- Always provide || {} / || [] defaults on possibly-missing sources
- Validate response shapes before destructuring
- Use safe navigation for nested paths
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
- groupBy called with null or undefined items
- invalid shorthand initializer: only allowed in…
- duplicate binding name
- invalid destructuring: rest element must be the last…
- invalid destructuring: rest element cannot have an…
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)