Objects
ObjectBuilder — shape syntax, composition, pick, omit, partial, required
w.object() creates schemas for objects with named, typed properties.
Shape Syntax
Pass a record of property name to builder:
const user = w.object({
name: w.string().trim().minLen(1),
age: w.integer().min(0),
email: w.string().email(),
});All properties are required by default.
Optional Properties
Mark individual properties as optional with .optional():
const user = w.object({
name: w.string(),
nickname: w.string().optional(), // Can be undefined
age: w.integer(),
});Or use the optional() helper:
import { w, optional } from "wellformed-ts";
const user = w.object({
name: w.string(),
nickname: optional(w.string()),
});Adding Properties
Use .prop() to add properties imperatively:
const schema = w.object()
.prop("name", w.string())
.prop("age", w.integer())
.prop("nickname", w.string(), false); // false = optionalAdditional Properties
By default, unknown properties are rejected. Pick the behavior explicitly so API boundaries are clear:
w.object({ name: w.string() }).strict()
// Reject unknown keys. This is also the default.
w.object({ name: w.string() }).strip()
// Omit unknown keys from the validated output.
w.object({ name: w.string() }).passthrough()
// Keep unknown keys without validating them.
w.object({ name: w.string() }).catchall(w.string())
// Keep unknown keys, but validate every unknown value as a string.additionalProperties(true) is available for IR compatibility and maps to passthrough behavior. New code should prefer .strict(), .strip(), .passthrough(), or .catchall(...) because the intent is clearer.
Composition
extend
Add new properties to an existing schema:
const base = w.object({ id: w.string() });
const user = base.extend({
name: w.string(),
age: w.integer(),
});
// { id: string; name: string; age: number }merge
Merge two object schemas:
const a = w.object({ name: w.string() });
const b = w.object({ age: w.integer() });
const merged = a.merge(b);
// { name: string; age: number }Rules and additional properties settings are combined from both schemas.
pick
Select specific properties:
const user = w.object({
id: w.string(),
name: w.string(),
email: w.string(),
});
const nameOnly = user.pick("name");
// { name: string }
const nameAndEmail = user.pick("name", "email");
// { name: string; email: string }omit
Remove specific properties:
const safe = user.omit("password");
// All fields except passwordpartial
Make all properties optional:
const partialUser = user.partial();
// { id?: string; name?: string; email?: string }required
Make all properties required:
const fullUser = partialUser.required();
// { id: string; name: string; email: string }Type Inference
All composition operations preserve type inference:
import type { Infer } from "wellformed-ts";
const schema = w.object({
name: w.string(),
age: w.integer().optional(),
}).extend({
email: w.string().email(),
});
type T = Infer<typeof schema>;
// { name: string; age?: number; email: string }Cross-Field Rules
Object rules run after all properties have been transformed and validated. This means a rule reads normalized values:
const schema = w.object({
ssn: w.string().digitsOnly().optional(),
country: w.string().upper(),
})
.when("country").equals("US").require("ssn");If the input contains { country: "us" }, the rule sees country as "US".