Documentation / Object

Object

Utilities for transforming and copying plain objects. Import from the root or from koyojs/Object.

// Root import
import { pick, omit, extract, exclude, cloneDeep, cloneShallow } from "koyojs";

// Per-module import
import { extract } from "koyojs/Object";
pickpick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>

Returns a new object containing only the specified top-level keys from the source object. The original is not mutated. TypeScript infers the return type as `Pick<T, K>` so callers retain full type safety. For nested dot-notation paths, use `extract`.

ParameterTypeDescription
objTThe source object.
keysK[]An array of top-level keys to keep.
returnsPick<T, K>
import { pick } from "koyojs";

const user = { id: 1, name: "Alice", email: "alice@example.com", role: "admin" };

pick(user, ["id", "name"]);
// { id: 1, name: "Alice" }

pick(user, ["email"]);
// { email: "alice@example.com" }

// Strip sensitive fields before sending to the client
const fullUser = {
  id: 42,
  name: "Alice",
  email: "alice@example.com",
  passwordHash: "...",
  sessionToken: "...",
};
const publicProfile = pick(fullUser, ["id", "name", "email"]);
// { id: 42, name: "Alice", email: "alice@example.com" }

// Build a PATCH payload from only the changed fields
const changes = pick(formState, ["name", "bio"]);
await api.patch(`/users/${userId}`, changes);

omitomit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>

Returns a new object with the specified top-level keys removed. The inverse of `pick`. The original is not mutated. Return type is `Omit<T, K>`. For nested dot-notation paths, use `exclude`.

ParameterTypeDescription
objTThe source object.
keysK[]An array of top-level keys to exclude.
returnsOmit<T, K>
import { omit } from "koyojs";

const user = { id: 1, name: "Alice", password: "s3cr3t", token: "abc" };

omit(user, ["password", "token"]);
// { id: 1, name: "Alice" }

omit(user, ["id"]);
// { name: "Alice", password: "s3cr3t", token: "abc" }

// Sanitize an object before logging — strip secrets
function logRequest(req: Request & { body: Record<string, unknown> }) {
  logger.info(omit(req.body, ["password", "creditCard", "ssn"]));
}

// Store a user record without internal tracking fields
const toStore = omit(incomingUser, ["__v", "_id", "createdAt"]);
await db.users.insert(toStore);

extractextract<T extends object>(obj: T, paths: string[]): Record<string, unknown>

Like `pick`, but resolves dot-notation nested paths instead of top-level keys. Each path string is split on `.` and the matching leaf value is lifted into a reconstructed nested result object. Missing paths are silently skipped.

ParameterTypeDescription
objTThe source object.
pathsstring[]Dot-notation paths to extract, e.g. `['a.b.c', 'd']`.
returnsRecord<string, unknown>
import { extract } from "koyojs";

const config = {
  db: { host: "localhost", port: 5432, password: "secret" },
  app: { port: 3000, debug: true },
};

extract(config, ["db.host", "db.port", "app.port"]);
// { db: { host: "localhost", port: 5432 }, app: { port: 3000 } }

// Missing paths are skipped silently — no error thrown
extract(config, ["db.host", "db.missing"]);
// { db: { host: "localhost" } }

// Pull specific fields from a deeply nested API response
const response = {
  user: {
    profile: { name: "Alice", bio: "Engineer" },
    auth:    { token: "xyz", expiresAt: "2026-01-01" },
  },
  meta: { requestId: "abc123", latencyMs: 42 },
};

extract(response, ["user.profile.name", "meta.requestId"]);
// { user: { profile: { name: "Alice" } }, meta: { requestId: "abc123" } }

// Build a safe subset of a config to pass to a child service
const safe = extract(fullConfig, ["db.host", "db.port", "app.port"]);

excludeexclude<T extends object>(obj: T, paths: string[]): Record<string, unknown>

Like `omit`, but resolves dot-notation nested paths instead of top-level keys. Removes only the specified leaf, leaving sibling keys and parent objects intact. Operates recursively, so multiple levels can be stripped in one call.

ParameterTypeDescription
objTThe source object.
pathsstring[]Dot-notation paths to remove, e.g. `['db.password']`.
returnsRecord<string, unknown>
import { exclude } from "koyojs";

const config = {
  db: { host: "localhost", port: 5432, password: "secret" },
  app: { port: 3000, debug: true },
};

exclude(config, ["db.password"]);
// { db: { host: "localhost", port: 5432 }, app: { port: 3000, debug: true } }

// Multiple paths — strip password and debug flag
exclude(config, ["db.password", "app.debug"]);
// { db: { host: "localhost", port: 5432 }, app: { port: 3000 } }

// Redact PII from a nested user record before logging
const userRecord = {
  id: 99,
  profile: { name: "Alice", ssn: "123-45-6789", email: "a@b.com" },
  payment: { last4: "4242", cvv: "123", expiry: "12/26" },
};

const safe = exclude(userRecord, ["profile.ssn", "payment.cvv"]);
// {
//   id: 99,
//   profile: { name: "Alice", email: "a@b.com" },
//   payment: { last4: "4242", expiry: "12/26" },
// }
logger.info(safe);  // cvv and ssn never leave the server

cloneDeepcloneDeep<T>(value: T): T

Returns a fully independent deep copy via a recursive walk. Preserves prototypes (instanceof stays intact), handles circular references, clones Map and Set entries, copies Date and RegExp, and preserves getter/setter descriptors instead of flattening them to values. Safe for class instances and circular graphs where `cloneShallow` would throw.

ParameterTypeDescription
valueTThe value to deep-clone.
returnsT
import { cloneDeep } from "koyojs";

const original = { a: 1, b: { c: [1, 2, 3] } };
const copy = cloneDeep(original);

copy.b.c.push(4);
console.log(original.b.c); // [1, 2, 3]  — untouched

// Preserves prototypes
class Point { constructor(public x: number, public y: number) {} }
const p = new Point(1, 2);
const q = cloneDeep(p);
console.log(q instanceof Point); // true

// Handles circular references
const circ: any = { x: 1 };
circ.self = circ;
const safe = cloneDeep(circ);
console.log(safe.self === safe); // true

// Clones Map and Set
const m = new Map([["a", { v: 1 }]]);
const m2 = cloneDeep(m);
m2.get("a")!.v = 99;
console.log(m.get("a")!.v); // 1  — untouched

cloneShallowcloneShallow<T>(value: T): T

Returns a deep copy using the platform's built-in `structuredClone`. Fast and correct for plain data structures (objects, arrays, dates, maps, sets). Throws a DataCloneError for non-serializable values such as functions, symbols, and class instances with methods. Use `cloneDeep` when you need prototype preservation or circular-reference safety.

ParameterTypeDescription
valueTThe value to clone.
returnsT
import { cloneShallow } from "koyojs";

const original = { a: 1, b: { c: [1, 2, 3] } };
const copy = cloneShallow(original);

copy.b.c.push(4);
console.log(original.b.c); // [1, 2, 3]  — untouched

// Works with dates, maps, sets
const withDate = { ts: new Date("2026-01-01") };
const cloned = cloneShallow(withDate);
cloned.ts.setFullYear(2030);
console.log(withDate.ts.getFullYear()); // 2026  — untouched

// Throws for non-serializable values
cloneShallow(() => {});         // DataCloneError
cloneShallow(new MyClass());    // DataCloneError (if MyClass has methods)