Typed utilities for string-keyed dictionaries.
Inputs requiring plain objects are validated consistently with
assert.dict(). Methods return new dictionaries
unless their documentation states that the input itself is returned.
import dict from "@rcompat/dict";dict.empty#
Returns whether a dictionary has no own enumerable keys.
dict.empty(dict: Dict): boolean;Example
dict.empty({}); // true
dict.empty({ ready: true }); // falsedict.entries#
Returns typed own enumerable key/value pairs.
dict.entries<T extends Dict>(dict: T): [keyof T, T[keyof T]][];Example
dict.entries({ north: 1, south: 2 });
// [["north", 1], ["south", 2]]dict.exclude#
Returns a copy without the selected keys.
dict.exclude<T extends Dict, const E extends keyof T>(
dict: T,
excludes: readonly E[],
): Omit<T, E>;Example
dict.exclude({ name: "Ada", password: "secret" }, ["password"]);
// { name: "Ada" }dict.filter#
Returns entries accepted by a key/value predicate.
dict.filter<T extends Dict>(
dict: T,
predicate: (key: keyof T, value: T[keyof T]) => boolean,
): Partial<T>;Example
dict.filter({ a: 1, b: 2 }, (_key, value) => value > 1);
// { b: 2 }dict.flatMap#
Maps entries and omits entries whose mapper returns an empty tuple.
dict.flatMap<T extends Dict>(
dict: T,
mapper: (key: keyof T, value: T[keyof T]) => [string, unknown] | [],
): Dict;Example
dict.flatMap({ a: 1, b: 2 }, (key, value) =>
value > 1 ? [key.toUpperCase(), value * 10] : []);
// { B: 20 }dict.get#
Reads a value from a dot-separated nested path.
dict.get<T extends Dict, P extends string>(
dict: T,
path: P,
): GetResultType<T, P>["result"];Example
dict.get({ server: { port: 8080 } }, "server.port"); // 8080dict.has#
Checks for an own property and narrows the key to the dictionary keys.
dict.has<D extends Dict>(dict: D, key: PropertyKey): key is keyof D;Example
dict.has({ name: "Ada" }, "name"); // true
dict.has({ name: "Ada" }, "age"); // falsedict.inflate#
Creates a nested dictionary from a path.
The by argument selects the path separator and defaults to ".".
dict.inflate<P extends string>(path: P): Inflated<P, Dict, ".">;
dict.inflate<P extends string, T>(path: P, initial: T): Inflated<P, T, ".">;
dict.inflate<
P extends string,
T,
B extends string>(path: P, initial: T, by: B): Inflated<P, T, B>;Example
dict.inflate("server.tls", { enabled: true });
// { server: { tls: { enabled: true } } }
dict.inflate("server/tls", { enabled: true }, "/");
// { server: { tls: { enabled: true } } }dict.keys#
Returns typed own enumerable keys.
dict.keys<T extends Dict>(dict: T): (keyof T)[];Example
dict.keys({ north: 1, south: 2 }); // ["north", "south"]dict.new#
Creates a frozen, null-prototype copy of a plain object.
dict.new<T extends object>(dict: T): T;Example
const config = dict.new({ mode: "production" });
Object.isFrozen(config); // true
Object.getPrototypeOf(config); // nulldict.map#
Maps values while preserving keys.
dict.map<T extends Dict, U>(
dict: T,
mapper: (key: keyof T, value: T[keyof T]) => U,
): { [K in keyof T]: U };Example
dict.map({ a: 1, b: 2 }, (_key, value) => value * 10);
// { a: 10, b: 20 }dict.mapKey#
Maps keys while preserving values.
dict.mapKey<T extends Dict>(
dict: T,
mapper: (key: keyof T, value: T[keyof T]) => string,
): Dict<T[keyof T]>;Example
dict.mapKey({ north: 1, south: 2 }, key => key.toUpperCase());
// { NORTH: 1, SOUTH: 2 }dict.override#
Recursively overlays one dictionary with another.
Values recognized by is.dict() are merged
recursively; other overriding values replace their base values.
Example
dict.override(
{ server: { host: "localhost", port: 3000 } },
{ server: { port: 8080 } },
);
// { server: { host: "localhost", port: 8080 } }dict.toQueryString#
Joins string entries as unencoded key=value pairs.
dict.toQueryString(dict: Dict<string>): string;Example
dict.toQueryString({ page: "1", sort: "name" });
// "page=1&sort=name"dict.values#
Returns typed own enumerable values.
dict.values<T extends Dict>(dict: T): T[keyof T][];Example
dict.values({ north: 1, south: 2 }); // [1, 2]Supporting contracts#
GetResultType#
Resolves the value type at a dot-separated dictionary path.
Type parameters
T— Dictionary type to traverse.P— Dot-separated path.D— Internal recursion-depth counter.
interface GetResultType<T, P, D extends number[] = []> {
result: D["length"] extends 6
? unknown
: P extends `${infer P1}.${infer Rest}`
? P1 extends keyof T
? GetResultType<Exclude<T[P1], undefined>, Rest, [...D, 0]>["result"]
| (T[P1] & undefined)
: undefined
: P extends keyof T
? T[P]
: undefined;
}Inflated#
Builds a nested dictionary type from a path and separator.
Type parameters
P— Path to expand.T— Leaf value type.B— Path separator.
type Inflated<P extends string, T, B extends string> =
P extends `${infer P1}${B}${infer Rest}` ?
{ [K in P1]: Inflated<Rest, T, B> } :
{ [K in P]: T };