Type-safe nested object access and manipulation for TypeScript. Full autocompletion, zero runtime errors, zero dependencies.
import { safePath, s } from 'ts-safe-path';
const data = {
user: {
name: 'John',
profile: {
address: { city: 'Paris', country: 'France' },
},
preferences: { theme: 'dark', notifications: true },
},
};
const sp = safePath(data);
// Get - full autocompletion and type inference
sp.get('user.profile.address.city'); // "Paris" (type: string | undefined)
// Set - type-checked values
sp.set('user.name', 'Jane');
// Has - check path existence
sp.has('user.profile.address'); // true
// Update - functional updates
sp.update('user.name', (current) => current?.toUpperCase() ?? 'ANONYMOUS');
// Delete - safe property removal
sp.delete('user.preferences.theme');
// Merge - deep merge preserving existing data
sp.merge({ user: { preferences: { theme: 'light' } } });
// Validate - schema validation
const result = sp.validate('user.name', s.string().min(2));
if (result.success) console.log(result.data);All mutating operations support an immutable option that returns a new object, leaving the original unchanged:
const original = { user: { name: 'John' } };
const sp = safePath(original);
const updated = sp.set('user.name', 'Jane', { immutable: true });
// original.user.name === "John"
// updated.user.name === "Jane"Works with set, delete, update, and merge.
Built-in validation with the s schema builder:
import { s } from 'ts-safe-path';
// Primitives
s.string(); // .min(n) .max(n) .email() .url() .regex(pattern)
s.number(); // .min(n) .max(n) .int() .positive()
s.boolean();
// Composites
s.array(s.string());
s.object({ name: s.string(), age: s.number() });
// Modifiers (available on all validators)
s.string().optional(); // allows undefined
s.string().nullable(); // allows null
s.string().default('fallback'); // default for undefined/null
s.string().transform((str) => str.trim()); // transform after validationconst sp = safePath(data);
// Validate value at path
const result = sp.validate('user.email', s.string().email());
if (!result.success) {
result.errors.forEach((e) => console.log(e.message));
}
// Validate and set in one step (throws on failure)
sp.validateAndSet('user.age', 25, s.number().min(0).max(120));
// Non-strict mode: returns original object on validation failure
sp.validateAndSet('user.age', 'bad', s.number(), { strict: false });const schema = s.object({
name: s
.string()
.min(2)
.transform((n) => n.trim()),
email: s.string().email(),
age: s.number().min(13).optional(),
});
const result = schema.validate(inputData);
if (result.success) {
// result.data is fully typed
}
// Or throw on failure
const data = schema.parse(inputData);Use standalone functions without creating a safePath instance:
import {
getValueByPath,
setValueByPath,
hasPath,
deletePath,
isValidPath,
getAllPaths,
clearPathCache,
} from 'ts-safe-path';
getValueByPath(obj, 'user.name'); // get value
setValueByPath(obj, 'user.name', 'Jane'); // set value
hasPath(obj, 'user.name'); // check existence
deletePath(obj, 'user.name'); // delete property
isValidPath(obj, 'user.name'); // runtime path validation
getAllPaths(obj); // discover all paths
clearPathCache(); // clear internal path parsing cache| Method | Returns |
|---|---|
get(path) |
PathValue<T, P> | undefined |
set(path, value, options?) |
T |
has(path) |
boolean |
delete(path, options?) |
T |
update(path, fn, options?) |
T |
merge(partial, options?) |
T |
getAllPaths() |
PathKeys<T>[] |
isValidPath(path) |
boolean |
validate(path, schema) |
ValidationResult<PathValue<T, P>> |
validateAndSet(path, value, schema, options?) |
T |
Options: { immutable?: boolean } for set/delete/update/merge.
ValidatedOptions: Also accepts { strict?: boolean } (default true, throws on failure).
MIT
