hx data Path Utilities - bradwoo8621/hx GitHub Wiki
@hx/data provides standalone path functions (get, set, parsePath) and powerful TypeScript type-level utilities for type-safe deep object access.
Split a dot-delimited path string into segments.
import { parsePath } from '@hx/data';
parsePath('user.name'); // ['user', 'name']
parsePath('user.addresses.[0].city'); // ['user', 'addresses', '[0]', 'city']Deeply read a value. Returns undefined if any segment doesn't exist.
import { get } from '@hx/data';
const obj = { user: { name: 'John', tags: ['a', 'b'] } };
get(obj, 'user.name'); // 'John'
get(obj, 'user.tags.[0]'); // 'a'
get(obj, 'user.age'); // undefinedType-safe — the return type is inferred from the path:
const obj = { user: { name: 'John', age: 30 } };
const name = get(obj, 'user.name'); // type: string | undefined
const age = get(obj, 'user.age'); // type: number | undefinedDeeply write a value. Creates intermediate objects/arrays automatically. Mutates in-place.
import { set } from '@hx/data';
const obj = { user: {} };
// Create intermediate objects
set(obj, 'user.address.city', 'NYC');
// obj === { user: { address: { city: 'NYC' } } }
// Create intermediate arrays
set(obj, 'items.[0].name', 'first');
// obj === { user: { ... }, items: [{ name: 'first' }] }Array behavior:
- Index out of bounds → array is extended with
nullfillers - Next segment is
[N]→ create array intermediate - Next segment is property → create object intermediate
Union of all valid path strings for type T. Handles nested objects and arrays.
import type { ModelPath } from '@hx/data';
interface User {
name: string;
address: {
city: string;
zip: string;
};
tags: string[];
}
type UserPaths = ModelPath<User>;
// "name" | "address" | "address.city" | "address.zip" | "tags" | "tags.[0]"Array types generate [number] paths:
type TagPaths = ModelPath<string[]>;
// "[0]"Get the value type at a specific path. Returns never for invalid paths.
import type { PathValue } from '@hx/data';
interface State {
count: number;
user: {
name: string;
tags: string[];
};
}
type T1 = PathValue<State, 'count'>; // number
type T2 = PathValue<State, 'user.name'>; // string
type T3 = PathValue<State, 'user.tags.[0]'>; // string
type T4 = PathValue<State, 'invalid'>; // neverLike PathValue but P is constrained to ModelPath<T> — only valid paths allowed at the type level.
type Valid = StrictPathValue<State, 'user.name'>; // string
type Invalid = StrictPathValue<State, 'bad'>; // never (compile error)Always '.'. Used internally by conditional types.
The type-level types integrate with the runtime functions:
function deepGet<T, P extends ModelPath<T>>(obj: T, path: P): PathValue<T, P> | undefined {
return get(obj, path);
}
// Fully type-safe:
const obj = { user: { name: 'John', age: 30 } };
const name = deepGet(obj, 'user.name'); // typeof name = string | undefined
const age = deepGet(obj, 'user.age'); // typeof age = number | undefined
// deepGet(obj, 'bad.path'); // TYPE ERROR! 'bad.path' is not assignableset({}, 'a.b', null);
// creates { a: { b: null } }set({ user: 'string' }, 'user.[0]', 1);
// THROWS: Cannot access index [0] on non-arrayconst obj = { a: { b: 1, c: 2 } };
set(obj, 'a.b', null); // creates obj.a.b = null (does NOT delete)