Selectors
Reference for all built-in selectors.
Overview
A selector is a string that points to one or more nodes inside an object or array. Selectors are accepted by filters and redactions to identify which parts of the snapshot to transform.
// remove a nested field
new ExcludeFilter(".user.password");
// redact every token in an array
new ReplacedRedaction(".tokens[]", "[TOKEN]");
// sort a deeply-nested array
new SortedRedaction(".**.tags");Selectors compose: segments are simply concatenated, so .user.address[0].street is read as
key user → key address → index 0 → key street.
Syntax
| Selector | Matches |
|---|---|
.key | Property key on an object |
["key"] | Same, bracket notation |
.a.b.c | Nested path |
[n] | Array item at index n |
[-1] | Last array item |
[] | All array items |
[start:end] | Array slice (negative indices supported) |
.* | All keys of an object (wildcard) |
.** | All descendants (recursive) |
.**.key | All key fields anywhere in the tree |
TypeScript support
Selector<T> is a template-literal type that computes every valid path for a given type.
Pass your data type as the generic argument and the selector string becomes type-checked:
type User = { name: string; address: { city: string; zip: string } };
// valid selectors inferred from User
new ExcludeFilter<User>(".address.city"); // ✓
new ExcludeFilter<User>(".address"); // ✓
new ExcludeFilter<User>(".missing"); // ✗ type errorAlternatively, let TypeScript infer the type from the value passed to a matcher:
const user = { name: "Alice", address: { city: "Paris", zip: "75001" } };
await expect(user).toJsonSnapshot({
name: "user",
redactions: [
new ReplacedRedaction(".address.zip", "[ZIP]"), // type-checked against User
],
});The type is resolved recursively up to a depth of 8 levels. For unknown or any inputs
the selector falls back to an unconstrained string, accepting any valid segment sequence.
NoInfer and type inference
Selector arguments use NoInfer<Selector<T>> internally, which prevents TypeScript from
inferring T from the selector string itself. This forces T to be resolved from the object
being tested, keeping the constraint meaningful.
Examples
// replace a top-level secret field
await expect({ name: "Alice", password: "s3cr3t" }).toJsonSnapshot({
name: "user",
redactions: [new ReplacedRedaction(".password", "[REDACTED]")],
});
// snapshot: { "name": "Alice", "password": "[REDACTED_1]" }// replace every item in an array
await expect({ tokens: ["abc123", "def456"] }).toJsonSnapshot({
name: "tokens",
redactions: [new ReplacedRedaction(".tokens[]", "[TOKEN]")],
});
// snapshot: { "tokens": ["[TOKEN_1]", "[TOKEN_2]"] }// replace a password at any depth in the object
await expect(deeplyNestedUser).toJsonSnapshot({
name: "nested-user",
redactions: [new ReplacedRedaction(".**.password", "[REDACTED]")],
});// exclude a whole nested section
await expect(user).toJsonSnapshot({
name: "user-public",
filters: [new ExcludeFilter(".address")],
});// sort a deeply-nested array of tags
await expect(post).toJsonSnapshot({
name: "post",
redactions: [new SortedRedaction(".**.tags")],
});Last updated on