Skip to content

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.city
javascript
const teamPath = dotpath`department.team`;
const full = dotpath`company.${teamPath}.members`;
full.get(data); // resolves company.department.team.members

Multi-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 .path and .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: dotpath is better when the same path is reused across many objects (define once, call .get() many times). Optional chaining (?.) is simpler for one-off access.