Appearance
Dot-Path Property Access
When to use
Use this when you need to read deeply nested object properties using a path like user.profile.address.city. Useful for configuration lookups, parsed data traversal (OFX, XML, JSON APIs), and anywhere a property path is known at definition time but the target object arrives later.
The pattern
A tagged template literal that parses a dot-separated path and returns an object with a .get(obj) method for safe traversal. If any segment is missing, .get() returns undefined instead of throwing.
Basic usage
javascript
const path = dotpath`user.profile.address.city`;
path.get({ user: { profile: { address: { city: "NYC" } } } });
// => "NYC"
path.get({ user: {} });
// => undefined (no error)
path.path;
// => ["user", "profile", "address", "city"]
path.toString();
// => "user.profile.address.city"Interpolation
Template interpolation lets you build paths dynamically. String values and other DotPath instances both work:
javascript
const section = "profile";
const path = dotpath`user.${section}.address.city`;
path.get(data); // resolves user.profile.address.cityjavascript
const teamPath = dotpath`department.team`;
const full = dotpath`company.${teamPath}.members`;
full.get(data); // resolves company.department.team.membersMulti-line paths
Whitespace is stripped, so long paths can span multiple lines for readability:
javascript
const txPath = dotpath`
OFX.BANKMSGSRSV1
.STMTTRNRS
.STMTRS
.BANKTRANLIST
.STMTTRN
`;Real-world example: OFX parsing
Define a path map once, then use .get() on each parsed document:
javascript
const OFXPath = {
org: dotpath`OFX.SIGNONMSGSRSV1.SONRS.FI.ORG`,
bank: {
account: {
number: dotpath`
OFX.BANKMSGSRSV1
.STMTTRNRS.STMTRS
.BANKACCTFROM.ACCTID
`,
transactions: dotpath`
OFX.BANKMSGSRSV1
.STMTTRNRS.STMTRS
.BANKTRANLIST.STMTTRN
`,
},
},
};
function getAccountInfo(ofx) {
return {
org: OFXPath.org.get(ofx),
number: OFXPath.bank.account.number.get(ofx),
};
}Implementing dotpath()
The DotPath class stores a parsed array of path segments. .get(obj) traverses them safely, returning undefined if any segment is missing. The dotpath tagged template handles interpolation and whitespace.
javascript
export class DotPath {
constructor(path) {
this.path = path;
}
get(obj) {
let current = obj;
for (const segment of this.path) {
if (current == null) return undefined;
current = current[segment];
}
return current;
}
toString() {
return this.path.join(".");
}
static create(strings, ...values) {
const WHITESPACE = /\s+/g;
const formatted = strings.map(
str => str.replace(WHITESPACE, "")
);
const joined = String.raw(
{ raw: formatted }, ...values
);
const path = joined.split(".")
.filter(segment => segment !== "");
return new DotPath(path);
}
}
export function dotpath(strings, ...values) {
return DotPath.create(strings, ...values);
}Trade-offs
- Pros: Declarative path definitions, safe traversal (no
?.chains), supports interpolation and composition, paths are inspectable via.pathand.toString(). - Cons: Adds a small abstraction over optional chaining. Not suitable when paths are computed entirely at runtime from untrusted input — use a simple loop over an array of keys instead.
- Versus optional chaining:
dotpathis better when the same path is reused across many objects (define once, call.get()many times). Optional chaining (?.) is simpler for one-off access.