karatelabs/karate · error · JsErrorException (typeError)
cannot read properties of
Error message
cannot read properties of ${null|undefined} (reading ...) What it means
A TypeError mirroring the browser message 'cannot read properties of null/undefined', thrown when the target of a logical assignment (`||=`, `&&=`, `??=`) through a dot or bracket access is null or undefined. RequireObjectCoercible fires before any key conversion, so `obj?.x ??= v` on a missing base throws rather than silently skipping.
Solutions
- Guard the base object first: `if (a) a.b ??= value;`
- Provide defaults at the root: `const a = raw ?? {};` before nested logical assignments
- Use explicit optional chaining plus separate assignment: `if (a?.b == null && a) a.b = value;`
- Fix the data source so the intermediate object is always present
Example fix
// before
config.redis ??= { host: 'localhost' };
// after
config = config ?? {};
config.redis ??= { host: 'localhost' }; Defensive patterns
Strategy: type-guard
Validate before calling
// JS in the script, before the logical assignment
if (config == null) config = {};
if (config.redis == null) config.redis = {}; Type guard
function hasBase(obj, path) {
return path.split('.').every(seg => (obj = obj?.[seg]) !== null && obj !== undefined) || obj !== undefined;
}
// simpler precheck:
function isObject(v) { return v !== null && typeof v === 'object'; } Try / catch
try {
evalScript("config.redis ??= { host: 'localhost' };");
} catch (JsErrorException e) {
if (e.getMessage().startsWith("cannot read properties of")) {
evalScript("config = config ?? {}; config.redis ??= { host: 'localhost' };");
} else throw e;
} Prevention
- Initialize nested object shells before applying nested logical assignments
- Validate JSON fixtures for required intermediate keys before use
- Use optional chaining for reads and guard before writing
- Centralize a `ensurePath(obj, 'a.b.c')` helper in test setups
When it happens
Trigger: `a.b ||= value` (or `&&=`/`??=`) where `a` evaluates to null or undefined — e.g. reading a nested config path that was never set, or an API response missing the intermediate object.
Common situations: Optional-chaining used for reads but not for the assignment target (`a?.b ??= v` still throws when `a` is undefined since the chain short-circuits to undefined, not a reference); JSON fixtures missing a nested level; map lookups returning null.
Related errors
- cannot read properties of
- cannot read properties of
- object is null
- a class declaration may not be the body of
- a function declaration may not be the body of
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/38e327862a5dd8a1.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/PropertyAccess.java:427
if (context.isStopped()) yield null;
context.updateSlot(slot, newValue, trackingNode);
yield newValue;
}
yield logicalCompoundRefByName(node, context, operator, rhsNode, trackingNode);
}
case REF_DOT_EXPR, REF_BRACKET_EXPR -> {
AccessSite site = resolveWriteSite(node, context);
if (site == null || site == SHORT_CIRCUIT_SITE) yield Terms.UNDEFINED;
// JS-level throw inside the target or index eval surfaces as
// context.isStopped() (eval returns null on abrupt completion).
// Bail before doing any further checks so the in-flight throw
// — not our own TypeError — is what the catch sees.
if (context.isStopped()) yield null;
// RequireObjectCoercible fires before ToPropertyKey on the index —
// null/undefined target must throw TypeError before any toString
// on a non-primitive key is invoked.
if (site.target == null || site.target == Terms.UNDEFINED) {
throw JsErrorException.typeError("cannot read properties of "
+ (site.target == null ? "null" : "undefined"));
}
if (site.privateName != null) {
Object oldValue = PrivateAccess.get(site.target, site.privateName, context);
if (!shouldLogicalAssign(operator, oldValue)) yield oldValue;
Object newValue = Interpreter.eval(rhsNode, context);
if (context.isStopped()) yield null;
PrivateAccess.set(site.target, site.privateName, newValue, context);
yield newValue;
}
if (site.receiver != null) {
// super reference — the fused by-index/by-name workers
// below have no receiver seam; run the generic sequence.
Object oldValue = siteRead(site, context);
if (context.isStopped()) yield null;
if (!shouldLogicalAssign(operator, oldValue)) yield oldValue;
Object newValue = Interpreter.eval(rhsNode, context);
if (context.isStopped()) yield null;View on GitHub (pinned to a22eb90246)